- Posted by robert on Juni 13, 2009
Hier nochmal die Anforderung der Entwicklung (CTRL F12):
Die Tests sind grün. Nach der Umsetzung bleibt folgender Eindruck:
Positives
- Erst die Spezifikation zu schreiben fokussiert auf das Ziel
- Die Entwicklung geht einfach von der Hand
- Die Benamung von Klassen und Methoden sind selbstsprechend
Negatives
- Die Hilfsmethoden wollen nicht so recht passen
- Richtig Ästhetisch scheint der Code nicht (aber vielleicht ist as manchmal bei real-world code einfach so?)
Hier die Testklasse nach der Implementierung.
namespace Tests.Domain.Campsites
{
public class CampsiteImageSearchBehaviour : BaseTest
{
private int _createdImadeId { get { return _imageSetup.Created[8].Id; } }
public void Arrange_n_images_in_storage(int amountOfImages)
{
_nHibernateHelper.TruncateTableCampsiteImages();
_imageSetup.Add(amountOfImages).Persist();
}
private CampsiteImageSearchDescription Get_search_desc_for_id(int imageId)
{
var searchDesc = new CampsiteImageSearchDescription();
searchDesc.Filter.CampsiteImageIds.Add(imageId);
return searchDesc;
}
[Test]
public void Should_retrieve_pager_from_search()
{
Arrange_n_images_in_storage(10);
var searchDesc = Get_search_desc_for_id(_createdImadeId);
_campsiteImageService.GetBy(searchDesc);
Assert.That(searchDesc.PageCount, Is.EqualTo(1));
Assert.That(searchDesc.TotalItems, Is.EqualTo(1));
}
[Test]
public void Should_return_image_by_id_using_search_description()
{
Arrange_n_images_in_storage(10);
var searchDesc = Get_search_desc_for_id(_createdImadeId);
var campsiteImages = _campsiteImageService.GetBy(searchDesc);
Assert.That(campsiteImages.Count, Is.EqualTo(1));
Assert.That(campsiteImages[0].Id, Is.EqualTo(_createdImadeId));
}
}
}
- Posted by robert on Juni 13, 2009
Es hat ein wenig gedauert. Zunächst skeptisch, erreichen nun Behaviour Driven Development Einflüsse immer mehr meinen Entwicklungsalltag.
1. Schritt: Die zu erreichenden Entwicklungs-Aufgaben beschreiben:
Der Code teilt klar seine Intentionen mit.
- Posted by robert on Juni 11, 2009
In einem ItemPanelItem eines ListViews wird bei Mausklick ein Event gefeuert:

Wie erhalten wir das DataItem für diesen ListView?
//Click event des Bildes
private void btnShowDetails_Click(object sender, RoutedEventArgs e)
{
//auf der Suche nach dem zu Grunde liegenden DataItem
var displayItem = lvLogItems.GetDataItemItem<LogDisplayItem>(e);
//hier folgt die eigentlich Arbeit
}
“GetDataItem” ist eine ExtensionMethod die im visuellen Baum solange von Eltern-Element zu Eltern-Element wandert, bis das übergeordnete ListViewItem erreicht ist. Mit dem ListViewItem ist auch das DataItem zu erreichen.
Die Extension Method wird in folgender Klasse definiert und ist auch den nächsten Tagen in Speak-Lib zu finden.
public static class ListViewUtils
{
public static TDataItem GetDataItemItem<TDataItem>(this ListView listView, RoutedEventArgs e)
{
var dependencyObject = (DependencyObject)e.OriginalSource;
return GetDataItem<TDataItem>(dependencyObject, listView);
}
public static TDataItem GetDataItemItem<TDataItem>(this ListView listView, DependencyObject dependencyObject)
{
return GetDataItem<TDataItem>(dependencyObject, listView);
}
private static TDataItem GetDataItem<TDataItem>(DependencyObject dependencyObject, ListView listView)
{
while ((dependencyObject != null) && !(dependencyObject is ListViewItem))
dependencyObject = VisualTreeHelper.GetParent(dependencyObject);
return (TDataItem)listView.ItemContainerGenerator.ItemFromContainer(dependencyObject);
}
}
Ich hoffe es nützt:-)
- Posted by robert on Juni 11, 2009
Öffnet man ein WPF-Window über Quelltext aus einem UserControl heraus, dann landet das neue Fenster hinter dem zu öffnenden Fenster. Die Problemlösung ist das Zuweisen des Window Owners.
var logItemDetail = new frmLogItemDetail(logDisplayItem);
logItemDetail.Owner = this.FindLogicalAncestorByType<Window>();
logItemDetail.Show()
“FindLogicalAncestor” ist ein Extension Method für DependencyObjects und stammt aus dem WPF-Contrib Projekt und ist nun auch aus Bequemlichkeitsgründen in Speak-Lib zu finden.
- Posted by Oliver on Juni 10, 2009
Gestern ging wieder etwas Zeit drauf, als ich unsere Tests ganz machen wollte und die Tests für das UrlRewriting partout nicht in der Testsuite durchlaufen wollten. Einzeln ausgeführt funktionierte hingegen alles wunderbar.
Dieses Phänomen tritt immer mal wieder auf, und es liegt (natürlich ;-)) an den unterschiedlichen Ausgangssituationen, in denen der betroffene Test startet.
Ohne große Umschweife hier also drei potenzielle Fallen:
- Der Cache ist gefüllt oder unsere RepositoryDb-Basisklasse hat nach GetAll() eine Liste aller Elemente zwischengespeichert (siehe auch 2.)
- Einige Services haben einen Zustand und dieser ist anders, als im Test angenommen --> Autofac-Container recyclen
- Die DB wird durch Custom-SQL manipuliert (bspw. zur Vorbereitung eines Tests) und alle Businesslogik wird umgangen.
Wenn also Fehler mit Tests auftreten, bitte diese drei Punkte überprüfen. Für Punkt eins und zwei gibt es in der
BaseTest-Klasse die Methode
RecycleServiceContainerAndClearCache().
- Posted by robert on Juni 9, 2009
Folgendes Video zeigt eine WPF ListView mit 2Millionen einträgen:
Out Of the Box ist der WPF ListView rasant schnell. Verwendet man jedoch ein Semi-kompliziertes DataTemplate, bricht die Performance ein.
Der Problem-Listview
im Gegensatz zum Beispielvideo benötigt der folgende ListView schon bei 1490 Elementen eine Renderzeit von fast 10 Sekunden.
Hier die Definition des ListViews:
<ListView
Grid.Row="1"
x:Name="lvLogItems"
ItemsSource="{Binding Source={StaticResource myList}}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Visible"
HorizontalContentAlignment="Stretch"
HorizontalAlignment="Stretch"
Style="{StaticResource lbResultStyle}"
ItemContainerStyle="{StaticResource lbResultItemStyle}"
ItemTemplate="{StaticResource itemTemplate}">
</ListView>
Die Definition des ItemTemplates:
<DataTemplate x:Key="itemTemplate">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="1" />
</Grid.RowDefinitions>
<DockPanel Grid.Row="0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30" />
<ColumnDefinition />
<ColumnDefinition Width="130"/>
<ColumnDefinition Width="22"/>
</Grid.ColumnDefinitions>
<DockPanel Grid.Column="0" Margin="0,1,0,0" > <!--Background="AliceBlue"-->
<Image Name="imgStatus" Height="20" VerticalAlignment="Top"/>
</DockPanel>
<DockPanel Grid.Column="1" Margin="3,1,0,0"><!--Background="BurlyWood"-->
<TextBlock Text="{Binding Path=StatusText}" TextWrapping="Wrap"></TextBlock>
</DockPanel>
<Grid Grid.Column="2">
<Grid.RowDefinitions>
<RowDefinition Height="16" />
<RowDefinition Height="6" />
<RowDefinition Height="12" />
<RowDefinition Height="12" />
</Grid.RowDefinitions>
<DockPanel>
<TextBlock Grid.Row="0" Text="Dauer: " FontSize="16" FontWeight="ExtraLight" />
<TextBlock Grid.Row="1" Text="{Binding Path=Duration}" FontSize="16" FontWeight="bold"></TextBlock>
</DockPanel>
<DockPanel Grid.Row="2">
<TextBlock Grid.Row="0" Text="Von: " FontSize="11" Width="28" />
<TextBlock Text="{Binding Path=StartTime, StringFormat=\{0:MM:dd.yy HH:mm:ss\}}" FontSize="11"></TextBlock>
</DockPanel>
<DockPanel Grid.Row="3">
<TextBlock Grid.Row="0" Text="Bis: " FontSize="11" Width="28" />
<TextBlock Text="{Binding Path=EndTime, StringFormat=\{0:MM:dd.yy HH:mm:ss\}}" FontSize="11"></TextBlock>
</DockPanel>
</Grid>
<Image Grid.Column="3" Source="..\Resources\application_view_detail.png"
Margin="0,2,3,0"
Height="16" VerticalAlignment="Top"></Image>
<Image Grid.Column="3" Source="..\Resources\email.png"
Margin="0,20,3,0"
Height="16" VerticalAlignment="Top"></Image>
</Grid>
Hier die Defintion des ItemContainerStyles:
<Style x:Key="lbResultStyle" TargetType="ListView">
<Setter Property="Margin" Value="0"/>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate >
<StackPanel IsItemsHost="True"
Orientation="Vertical"
Width="{Binding ElementName=lvResult,Path=ActualWidth}" >
</StackPanel>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
VirtualizingStackPanel zur Hilfe
Die Reduzierung der zähen Ladezeit erfolgt über eine triviale Änderung: Statt der Verwendung des StackPanel als ItemHost wird ein “VirtualizingStackPanel“ verwendet:
<Style x:Key="lbResultStyle" TargetType="ListView">
<Setter Property="Margin" Value="0"/>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate >
<VirtualizingStackPanel IsItemsHost="True"
Orientation="Vertical"
Width="{Binding ElementName=lvResult,Path=ActualWidth}" >
</VirtualizingStackPanel>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
Der Effekt ist eine dramatische Verkürzung der 10-sekündígen Renderzeit hinzu einer sofortige Darstellung des Grids.
Wie funktioniert es?
Das Zauberwort in WPF heißt Virtualisierung: Der gewaltige Performance Unterschied wird erreicht, durch die Art und Weise wie das VirtualizingStackPanel die Scrollhöhe bestimmt. Statt die Höhe eines jeden Elements auszurechnen (und dafür zu rendern), wird nur der Index des Elements für die Bestimmung der ScrollPosition verwendet. Wird nun gescrollt müssen nur die Elemente gerendert werden, die tatsächlich sichtbar sind.
Lesenswert:
- Posted by robert on Juni 4, 2009
Mich hat dieser Post dazu inspiriert die Petition gegen Internetsperren zu unterschreiben. Ich bitte jeden, der es noch nicht gemacht hat, auch zu unterschreiben.
Hier mehr:
Link auf die Petition: http://zeichnemit.de/mitzeichnung.php
- Posted by robert on Juni 4, 2009
Folgender Code öffnet den “Default-Email-Client”:
System.Diagnostics.Process.Start(
String.Format("mailto:{0}?subject={1}&body={2}",
"demo@mail.de",
"Nachricht von test@test.de",
"Hallo %0aNachricht %0aGrußformel"));
Attachments werden nicht konsistent interpretiert und sind Email-Client Versions und Patch abhängig. “Attach” wird von Outlook 2007 aus Sicherheitsgründen nicht weiter unterstützt. Werden Attachments verbindlich benötigt ist man mit dem direkten Versand der Email auf der sicheren Seite.