Auf der CodeProject Website habe ich kürzlich einen Artikel veröffentlicht, welcher die Implementierung eines interessanten WPF Benutzersteuerelementes detalliert beschreibt. Dieses WPF Benutzersteuerelement (WPF Control) kann sehr einfach als Login-Overlay für eigene WPF-Anwendungen (ab. NET 3.5) verwendet werden. Es ähnelt der Windows 8 Anmeldemaske sehr und verhält sich auch nahezu identisch wie die Windows 8 Anmeldemaske.
Hier sind einige Screenshots des Benutzersteuerelementes :
Der vollständige Artikel ist verfügbar unter : http://www.codeproject.com/Articles/498253/Smart-WPF-Login-Overlay-Windows-8-Style (nur in Englisch)
Der Quellcode sowie eine Demo können unter dem angegebenen Link auch heruntergeladen werden. In der Demo ist ein Beispiel enthalten, wie das Benutzersteuerelement für eine "deutsche" Anwendung angepasst werden kann (siehe auch die obigen Screenshots).
EDIT : Bei dem UserControl mit den deutschen Texten muss folgende Eigenschaft noch gesetzt werden FullSpan="On"
(hatte ich versäumt … sorry). Und nicht wundern, dass das UserControl Transparent ist : Dies habe ich bewusst so implementiert, um zu zeigen, dass die Hintergrundfarbe des Root Fensters automatisch adaptiert wird, wenn auf dem UserControl keine Hintergrundfarbe gesetzt wurde.
Regarding to the following blog post http://dotnet-forum.de/blogs/thearchitect/archive/2012/11/06/windows-8-passwordbox-style-for-wpf.aspx i have furthermore improved the PasswordBox Style to look more similar to the Windows 8 PasswordBox. And again I did not use the VisualStateManager Class, so it is possible to use this style also with the .Net Framework 3.0 / 3.5 (and not only with the .Net Framework 4.0).
Here is how it looks like now :
This is the new corresponding style (Win8PasswordBoxStyle) :
<Style x:Key="Win8PasswordBoxStyle" TargetType="{x:Type PasswordBox}" BasedOn="{StaticResource {x:Type PasswordBox}}">
<Setter Property="Foreground" Value="{StaticResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="Background" Value="{StaticResource {x:Static SystemColors.WindowBrushKey}}"/>
<Setter Property="BorderBrush" Value="{StaticResource TextBoxBorder}"/>
<Setter Property="FontFamily" Value="Times New Roman"/>
<Setter Property="PasswordChar" Value="●"/>
<Setter Property="KeyboardNavigation.TabNavigation" Value="None"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Padding" Value="1"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="AllowDrop" Value="true"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type PasswordBox}">
<Border x:Name="OuterBorder"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
SnapsToDevicePixels="true">
<Grid VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ScrollViewer x:Name="PART_ContentHost" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
<TextBox x:Name="RevealedPassword"
Text="{TemplateBinding ap:PasswordBoxBinding.Password}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
VerticalContentAlignment="Center"
Padding="{TemplateBinding Padding}"
Margin="{TemplateBinding Padding}"
Background="{TemplateBinding Background}"
Visibility="Hidden" BorderThickness="0" IsReadOnly="True" FontFamily="Segoe UI" />
<TextBlock x:Name="TextBoxHint"
Text="Enter password"
Padding="{TemplateBinding Padding}"
Background="Transparent"
Cursor="IBeam" Margin="2" FontFamily="Segoe UI" Foreground="#FFB5B5B5"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Visibility="{Binding ElementName=RevealedPassword, Path=Text, Converter={StaticResource StringToOppositeVisibilityConverter}}" />
<Button x:Name="RevealButton"
Grid.Column="1" SnapsToDevicePixels="True"
Style="{StaticResource RevealButtonStyle}"
Visibility="{Binding ElementName=RevealedPassword, Path=Text, Converter={StaticResource StringToVisibilityConverter}}">
</Button>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Background" TargetName="OuterBorder" Value="{StaticResource {x:Static SystemColors.ControlBrushKey}}" />
<Setter Property="Foreground" Value="{StaticResource {x:Static SystemColors.GrayTextBrushKey}}" />
<Setter Property="Opacity" TargetName="RevealButton" Value="0.5" />
<Setter Property="Text" TargetName="TextBoxHint" Value="{}{disabled} "/>
</Trigger>
<DataTrigger Binding="{Binding ElementName=RevealButton, Path=IsPressed}" Value="True">
<Setter TargetName="RevealedPassword" Property="Visibility" Value="Visible" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
… and this is the corresponding RevealButton-Style :
<Style x:Key="RevealButtonStyle" TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Grid>
<Border x:Name="PasswordRevealGlyphBorder" Background="Transparent" Margin="1,1,3,1"
BorderThickness="{TemplateBinding BorderThickness}">
<TextBlock x:Name="GlyphElement" Foreground="Black" VerticalAlignment="Center" HorizontalAlignment="Center"
Text="" FontFamily="Segoe UI Symbol" Margin="3,0"
FontSize="{TemplateBinding FontSize}" />
</Border>
</Grid>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding ElementName=GlyphElement, Path=IsMouseOver}" Value="True">
<Setter TargetName="PasswordRevealGlyphBorder" Property="Background" Value="Gainsboro" />
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsPressed}" Value="True">
<Setter TargetName="PasswordRevealGlyphBorder" Property="Background" Value="Black" />
<Setter TargetName="GlyphElement" Property="Foreground" Value="White" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
You can download the sample application here :
SmartPasswordBoxDemo2.zip

If you enjoyed reading the article, then please "Kick It" :
EDIT : Check out the improved style here http://dotnet-forum.de/blogs/thearchitect/archive/2012/11/13/improved-windows-8-passwordbox-style-for-wpf.aspx
For everybody who is interested in a PasswordBox similar to the Windows 8 Login PasswordBox I have implemented a XAML-Style (available for WPF-Applications).
Here is how it looks like :
This is the corresponding style :
<Style x:Key="SmartPasswordBoxStyle" TargetType="{x:Type PasswordBox}" BasedOn="{StaticResource {x:Type PasswordBox}}">
<Setter Property="Foreground" Value="{StaticResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="Background" Value="{StaticResource {x:Static SystemColors.WindowBrushKey}}"/>
<Setter Property="BorderBrush" Value="{StaticResource TextBoxBorder}"/>
<Setter Property="FontFamily" Value="Times New Roman"/>
<Setter Property="PasswordChar" Value="●"/>
<Setter Property="KeyboardNavigation.TabNavigation" Value="None"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Padding" Value="1"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="AllowDrop" Value="true"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type PasswordBox}">
<Border x:Name="OuterBorder"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
SnapsToDevicePixels="true">
<Grid VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ScrollViewer x:Name="PART_ContentHost" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
<TextBox x:Name="RevealedPassword"
Text="{TemplateBinding ap:PasswordBoxBinding.Password}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
VerticalContentAlignment="Center"
Padding="{TemplateBinding Padding}"
Margin="{TemplateBinding Padding}"
Background="{TemplateBinding Background}"
Visibility="Hidden" BorderThickness="0" IsReadOnly="True" FontFamily="Segoe UI" />
<TextBlock x:Name="TextBoxHint"
Text="Enter password"
Padding="{TemplateBinding Padding}"
Background="Transparent"
Cursor="IBeam" Margin="2" FontFamily="Segoe UI" Foreground="#FFB5B5B5"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Visibility="{Binding ElementName=RevealedPassword, Path=Text,
Converter={StaticResource StringToOppositeVisibilityConverter}}" />
<Border x:Name="PasswordRevealImageBorder" Grid.Column="1" Margin="0,1,3,1"
Visibility="{Binding ElementName=RevealedPassword, Path=Text, Converter={StaticResource StringToVisibilityConverter}}">
<Image x:Name="PasswordRevealImage"
Source="../../CommonImages/PasswordEyeBlack.png" Margin="2" />
</Border>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Background" TargetName="OuterBorder" Value="{StaticResource {x:Static SystemColors.ControlBrushKey}}"/>
<Setter Property="Foreground" Value="{StaticResource {x:Static SystemColors.GrayTextBrushKey}}"/>
<Setter Property="Opacity" TargetName="PasswordRevealImageBorder" Value="0.5"/>
<Setter Property="Text" TargetName="TextBoxHint" Value="{}{disabled}"/>
</Trigger>
<DataTrigger Binding="{Binding ElementName=PasswordRevealImage, Path=IsMouseOver}" Value="True">
<Setter TargetName="PasswordRevealImageBorder" Property="Background" Value="Gainsboro" />
</DataTrigger>
<EventTrigger RoutedEvent="Image.MouseDown" SourceName="PasswordRevealImage">
<BeginStoryboard Storyboard="{StaticResource ShowPassword}"/>
</EventTrigger>
<EventTrigger RoutedEvent="Image.MouseUp" SourceName="PasswordRevealImage">
<BeginStoryboard Storyboard="{StaticResource HidePasswordAndChangeBorderColor}"/>
</EventTrigger>
<EventTrigger RoutedEvent="Image.MouseLeave" SourceName="PasswordRevealImage">
<BeginStoryboard Storyboard="{StaticResource HidePassword}"/>
</EventTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
As you can see you need a special AttachedProperty, some custom Storyboards an some Converters. So you have to reference the WPFSmartLibraryLight to make this working.
To give you an insight into the power of the WPFSmartLibrary I have put a link to a sample application at the end of this post including the demonstration of many cool functions. It is about a smart login dialog with almost no code behind (100% MVVM).
This sample application gives you a good overview to the following topics :
1. Very simple and easy to understand MVVM example
Login functionality with smart animations without code behind, except the following (needed) code behind :
InitializeComponent();
this.ViewModel = new LoginViewModel();
this.DataContext = this.ViewModel;
=> Also no code behind for the animation needed (solved with AttachedProperties)
2. Good demonstration of some WPFSmartLibrary - features
- SmartButtons
- SmartBackgroundBrushes
- WindowKeysHandling
- TextBoxBaseManager
- PasswordBoxManager
- BoolToBrushConverter
- BoolToVisibilityConverter
- TextBoxBaseAnimation
- VisibilityAnimation
- PasswordBoxBinding
- Validation Templates
- and this everything in one small demo …
You can download the sample application here :
EDIT : The source code has been updated, regarding issues with the WPF Designer ... sorry for possible inconvenience
SmartPasswordBoxDemo.zip
If you enjoyed reading the article, then please "Kick It" :
Es existieren viele unterschiedliche Implementierungen des INotifyPropertyChanged - Interfaces.
Ausgehend von der allgemein bekannten Standard-Implementierung werden in diesem Beitrag die gängigsten Implementierungen analysiert sowie die Vor- und Nachteile aufgelistet. Zusätzlich wird eine eigene Implementierung, welche bereits in diversen größeren dotNet-Projekten verwendet wurde, vorgestellt. Außerdem kann am Ende des Beitrages eine ViewModelBase-Klasse (C#-Datei welche alle Implementierungen enthält) heruntergeladen werden. Diese darf darüber hinaus auch in eigenen Projekten verwendet werden.
[1] Die Verwendung der typischen Standard-Implementierung
public string someProperty;
public string SomeProperty
{
get { return someProperty; }
set
{
if (someProperty != value)
{
someProperty = value;
RaisePropertyChangedEvent( "SomeProperty" );
}
}
}Nachteile
- Der übergebene String-Parameter wird bei der Eingabe nicht überprüft wird und man kann sich somit unbemerkt vertippen => mögliche
Fehlerquelle
- Bei einer Änderung des Bezeichners könnte man vergessen den String-Parameter mit anzupassen, dies würde auch unbemerkt bleiben => mögliche
Fehlerquelle
- Es muss immer ein extra Feld als "Datenspeicher" implementiert werden
- Das PropertyChanged-Event muss manuell gefeuert werden
[2] Die Verwendung einer verbesserten Standard-Implementierung
Hierbei wird zusätzlich überprüft, ob eine Eigenschaft mit der Bezeichnung des übergebenen String-Parameters existiert :
[Conditional( "DEBUG" )]
private void checkPropertyName(string propertyName)
{
PropertyDescriptor propertyDescriptor = TypeDescriptor.GetProperties( this )[propertyName];
if (propertyDescriptor == null)
{
string message = string.Format( null, "The property with the propertyName '{0}' doesn't exist.", propertyName );
Debug.Fail( message );
}
}
Nachteile
Alle Nachteile der Standardlösung, jedoch mit kleinen Unterschied, dass beim Debuggen festgestellt werden kann, ob eine Eigenschaft mit dem übergebenen Bezeichner auch wirklich existiert
[3] Die Verwendung einer nochmals verbesserten Standard-Implementierung
Hierbei wird der Parameter als Lambda-Expression übergeben :
public string someProperty;
public string SomeProperty
{
get { return someProperty; }
set
{
if (someProperty != value)
{
someProperty = value;
RaisePropertyChangedEvent( () => SomeProperty );
}
}
}
Vorteile
Die in den ersten beiden Implementierungen genannten Nachteile als mögliche Fehlerquellen fallen nun weg, weil der Bezeichner in der Lambda Expression eine existierende Eigenschaft sein muss, da ansonsten die IDE (Visual Studio) bzw. spätestens der Compiler feststellen würde, dass eine Eigenschaft mit diesem Bezeichner nicht existiert
Bei einer Änderung des Bezeichners und der automatischen Änderung durch die IDE (Visual Studio) wird der Parameter auch mit angepasst
Nachteile
Es muss immer ein extra Feld als "Datenspeicher" implementiert werden
Das PropertyChanged-Event muss manuell gefeuert werden
[4] Die Verwendung einer optimalen Standard-Implementierung
Hierbei wird das Event ohne der Übergabe eines Parameters gefeuert (identisch zu der Vorgehensweise im dotNet-Framework 4.5 bzw. C# 5.0) :
public string someProperty;
public string SomeProperty
{
get { return someProperty; }
set
{
if (someProperty != value)
{
someProperty = value;
RaisePropertyChangedEvent();
}
}
}
Implementierung
protected void RaisePropertyChangedEvent()
{
// Get the call stack
StackTrace stackTrace = new StackTrace();
// Get the calling method name
string callingMethodName = stackTrace.GetFrame( 1 ).GetMethod().Name;
// Check if the callingMethodName contains an underscore like in "set_SomeProperty"
if (callingMethodName.Contains( "_" ))
{
// Extract the property name
string propertyName = callingMethodName.Split( '_' )[1];
if (this.PropertyChanged != null && propertyName != String.Empty)
{
this.PropertyChanged( this, new PropertyChangedEventArgs( propertyName ) );
}
}
}
Vorteile
Die in den ersten beiden Implementierungen genannten Nachteile als mögliche Fehlerquellen fallen nun weg, da kein Parameter mehr übergeben werden muss
Eine Änderung des Bezeichners wirkt sich auch nicht problematisch aus, da wie gesagt kein Parameter übergeben wird
Nachteile
Es muss immer ein extra Feld als "Datenspeicher" implementiert werden
Das PropertyChanged-Event muss manuell gefeuert werden
[5] Die (meiner Meinung nach) optimale Implementierung
Hierbei wird jede Property auf die gleiche Weise, wie die zu den DependencyProperties gehörenden CLR-Wrapper implementiert. Bei den (zu den DependencyProperties) gehörenden CLR-Wrappern wird durch die Verwendung der Methoden GetValue bzw. SetValue das Property-System des .NET-Frameworks aktiv und führt diverse Evaluierungen im Hintergrund durch. Eine identische Vorgehensweise habe ich für eine optimale Implementierung des INotifyPropertyChanged-Interfaces verwendet.
Die Verwendung
public string SomeProperty
{
get { return GetValue( () => SomeProperty ); }
set { SetValue( () => SomeProperty, value ); }
}
Vorteile
Die in den ersten beiden Standard-Implementierungen genannten Nachteile als mögliche Fehlerquellen fallen nun weg, weil der Bezeichner in der Lambda Expression eine existierende Eigenschaft sein muss, da ansonsten die IDE (Visual Studio) bzw. spätestens der Compiler feststellen würde, dass eine Eigenschaft mit diesem Bezeichner nicht existiert
Bei einer Änderung des Bezeichners und der automatischen Änderung durch die IDE (Visual Studio) wird der Parameter auch mit angepasst
Ein extra Feld als "Datenspeicher" muss nicht mehr implementiert werden, da die Werte der einzelnen Eigenschaften automatische in einem Dictionary der ViewModelBase-Klasse verwaltet werden
Das Event muss nicht mehr manuell gefeuert werden, da dieses automatisch in der SetValue-Methode der ViewModelBase-Klasse erfolgt
Im Gegensatz zu den DependencyProperties des .NET-Frameworks muss der Rückgabewert der GetValue-Methode nicht mehr gecastet werden, da der Rückgabewert typensicher aus der Lambda-Expression ermittelt wird
Nachteile
KEINE
Da die (meiner Ansicht nach) optimale Implementierung ähnlich der Implementierung von DependencyProperties funktioniert, ist eine noch bessere Konsistenz in der Code Basis gewährleistet.
Implementierung
public abstract class ViewModelBase : INotifyPropertyChanged
{
#region < INotifyPropertyChanged > Members
/// <summary>
/// Is connected to a method which handle changes to a property (located in the WPF Data Binding Engine)
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raise the [PropertyChanged] event
/// </summary>
/// <param name="propertyName">The name of the property</param>
protected void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged( this, new PropertyChangedEventArgs( propertyName ) );
}
}
#endregion
private Dictionary<string, object> propertyValueStorage;
#region Constructor
public ViewModelBase()
{
this.propertyValueStorage = new Dictionary<string, object>();
}
#endregion
/// <summary>
/// Set the value of the property and raise the [PropertyChanged] event
/// (only if the saved value and the new value are not equal)
/// </summary>
/// <typeparam name="T">The property type</typeparam>
/// <param name="property">The property as a lambda expression</param>
/// <param name="value">The new value of the property</param>
protected void SetValue<T>(Expression<Func<T>> property, T value)
{
LambdaExpression lambdaExpression = property as LambdaExpression;
if (lambdaExpression == null)
{
throw new ArgumentException( "Invalid lambda expression", "Lambda expression return value can't be null" );
}
string propertyName = this.getPropertyName( lambdaExpression );
T storedValue = this.getValue<T>( propertyName );
if (!object.Equals( storedValue, value ))
{
this.propertyValueStorage[propertyName] = value;
this.OnPropertyChanged( propertyName );
}
}
/// <summary>
/// Get the value of the property
/// </summary>
/// <typeparam name="T">The property type</typeparam>
/// <param name="property">The property as a lambda expression</param>
/// <returns>The value of the given property (or the default value)</returns>
protected T GetValue<T>(Expression<Func<T>> property)
{
LambdaExpression lambdaExpression = property as LambdaExpression;
if (lambdaExpression == null)
{
throw new ArgumentException( "Invalid lambda expression", "Lambda expression return value can't be null" );
}
string propertyName = this.getPropertyName( lambdaExpression );
return getValue<T>( propertyName );
}
/// <summary>
/// Try to get the value from the internal dictionary of the given property name
/// </summary>
/// <typeparam name="T">The property type</typeparam>
/// <param name="propertyName">The name of the property</param>
/// <returns>Retrieve the value from the internal dictionary</returns>
private T getValue<T>(string propertyName)
{
object value;
if (propertyValueStorage.TryGetValue( propertyName, out value ))
{
return (T)value;
}
else
{
return default( T );
}
}
/// <summary>
/// Extract the property name from a lambda expression
/// </summary>
/// <param name="lambdaExpression">The lambda expression with the property</param>
/// <returns>The extracted property name</returns>
private string getPropertyName(LambdaExpression lambdaExpression)
{
MemberExpression memberExpression;
if (lambdaExpression.Body is UnaryExpression)
{
var unaryExpression = lambdaExpression.Body as UnaryExpression;
memberExpression = unaryExpression.Operand as MemberExpression;
}
else
{
memberExpression = lambdaExpression.Body as MemberExpression;
}
return memberExpression.Member.Name;
}
}
Die ViewModelBase-Klasse kann man sich hier herunterladen :
ViewModelBase.zip

Falls euch der Artikel gefallen sollte, dann bitte "kicken" :

Seit .NET 4.0 läuft die Zuweisung eines „OuterGlowBitmapEffektes“ ins Leere.
Das Ärgerliche bei dem Versuch diese Klasse zu verwenden ist, dass man im XAML-Code zunächst einmal nichts bemerkt (kein Hinweis im Visual Studio auf eine obsolete [veraltete bzw. ungültig gewordene] Klasse und auch keine Exception beim Ausführen der Applikation (ein Exception-Handling ist im XAML-Code ja nur bedingt möglich). Somit überprüft man erst andere mögliche Fehlerquellen um dann festzustellen, dass sowohl die BitmapEffect-Klasse als auch die OuterGlowBitmapEffect-Klasse weiterhin in .NET 4.0 existent sind, jedoch bei einer Zuweisung der gewünschte Effekt ausbleibt.
Im MSDN habe ich diesbezüglich folgenden Artikel gefunden :
http://msdn.microsoft.com/en-us/library/ms743435.aspx Da ich diesen Effekt jedoch für meine WPFSmartButtons benötige, habe ich als Workaround stattdessen die DropShadowEffect-Klasse verwendet, welche von der Effect-Klasse abgeleitet ist.
Statt z.B.
<OuterGlowBitmapEffect GlowColor="White" GlowSize="10" />
habe ich
<DropShadowEffect ShadowDepth="0" Color="White" BlurRadius="30" RenderingBias="Quality"/>
verwendet.
Der Effekt ist nahezu identisch, auch wenn geringfügige optische Abweichungen bestehen.
Screenshots der Beispielapplikation :
Bei Fragen und Anregungen schreibt mir doch einfach einen Kommentar oder eine PN.
Die Beispielapplikation könnt ihr euch hier herunterladen :
BitmapEffect.zip
Zum Pausieren einer Silverlight Applikation kann man nicht einfach die Methode Thread.Sleep( int millisecondsTimeout ) aus dem Namespace System.Threading verwenden, da dadurch nicht nur die Silverlight Applikation, sondern darüber hinaus auch der ganze Browser (in welchem die Applikation ja als Client ausgeführt wird) einfriert.
Als Workaround habe ich stattdessen ein Storyboard verwendet, welches eine frei bestimmbare Zeit einfach gar nichts macht. Dann habe ich dieses Storyboard in eine Methode verpackt, welche folgendermaßen aussieht :
private void SLThreadSleep( int TimeoutInMilliseconds )
{
// Storyboard für eine Pause von x Millisekunden erzeugen ...
Storyboard SleepStoryboard = new Storyboard();
DoubleAnimation SleepDuration = new DoubleAnimation() { Duration = TimeSpan.FromMilliseconds( TimeoutInMilliseconds ), From = 1, To = 1 };
SleepStoryboard.Children.Add( SleepDuration );
Storyboard.SetTarget( SleepDuration, LayoutRoot );
Storyboard.SetTargetProperty( SleepDuration, new PropertyPath( "(UIElement.Opacity)" ) );
SleepStoryboard.Completed += new EventHandler( (ds, de) =>
{
// Master Grid und gesamten Inhalt wieder aktivieren
LayoutRoot.IsHitTestVisible = true;
// Das Rücksetzen des Buttontextes ist nur für die Beispielapplikation gedacht
btnSleep2.Content = "SL App 5 Sekunden pausieren (mit Storyboard)";
} );
SleepStoryboard.Begin();
}
(Bei LayoutRoot handelt es sich um das sogenannte Master-Grid, welches sich im Logical Tree an oberster Stelle befindet)
Bei Fragen und Anregungen schreibt mir doch einfach einen Kommentar oder eine PN.
Die Beispielapplikation könnt ihr euch hier herunterladen :
Sl4ThreadSleep.zip
Nachdem ich gestern bei einem Film die Einblendungen der „credits“ im Vorspann sah, dachte ich mir : „Das geht doch bestimmt auch ganz einfach mit WPF“.
Habe mich dann auch dran gesetzt und eine kleine Extension-Methode geschrieben, mit welcher man jeden beliebigen TextBlock um eine „credits“-ähnliche Animation erweitern kann. Die Extension-Methode sieht folgendermaßen aus :
public static void AddSmartFadingAnimation( this TextBlock TextBlockToAnimate,
List<string> FadingTextList,
double TextPromptDuration,
double FadingSpeed )
Die Verwendung der Methode ist recht einfach. Man legt einfach ein TextBlock-Control in seiner GUI an, weist dem TextBlock-Control alle gewünschten Eigenschaften (Properties) zu, erstellt ein String-Collection (List<sring>), füllt diese mit den entsprechenden Texten ( .add(„Text“) ) und wendet die (Extension-)Methode
AddSmartFadingAnimation(...) auf dem TextBlock-Objekt an. Eine beispielhafte Verwendung der Methode sieht folgendermaßen aus :
List<string> TextList = new List<string>();
for( int i = 0; i < 10; i++ )
TextList.Add( "Einzublendender Text Nr. " + i );
tblFadingInfo.AddSmartFadingAnimation( TextList, 1000, 1500 );
(tblFadingInfo ist hierbei der Name eines beispielhaften TextBlock-Objektes) Screenshots der Beispielapplikation :


Bei Fragen und Anregungen schreibt mir doch einfach einen Kommentar oder eine PN.
Die Beispielapplikation könnt ihr euch hier herunterladen :
SmartTextFading.zip
Screenshots des einfachen Templates ( SL4 - WP7 ) :
Screenshots des erweiterten Templates ( SL4 ) :

Screenshots des erweiterten Templates ( WP7 ) :


Wie man erkennen kann, sind optische Unterschiede kaum noch vorhanden.
Wichtig ist es jedoch die nachfolgenden Unterschiede zwischen der SL4 App und der WP7 App zu beachten.
In der
WMAppManifest.xml folgende Änderung vornehmen :
<Tasks>
<DefaultTask Name ="_default" NavigationPage="Page1.xaml"/>
</Tasks>
Das URI-Mapping muss in die
App.xaml verlagert werden und zusätzlich muss folgender Code in den Konstruktor der
App.xaml.cs eingefügt werden :
RootFrame.UriMapper = Resources["UriMapper"] as UriMapper;
Bei Fragen und Anregungen schreibt mir doch einfach einen Kommentar oder eine PN.
Das Projekt mit den beiden Templates könnt ihr euch hier herunterladen :
SL4WP7Templates.zip
WP7ExtTemplate.zip
Bei einer kürzlich durchgeführten Entwicklung einer grafischen Oberfläche (GUI) für eine Windows Phone 7 (WP7) App bin ich folgendermaßen vorgegangen :
Die Tools bestehen aus folgenden Komponenten :
• Visual Studio 2010 Express for Windows Phone
• Windows Phone Emulator Resources
• Silverlight 4 Tools für Visual Studio
• XNA Game Studio 4.0
• Microsoft Expression Blend for Windows Phone
Dies bedeutet jedoch, dass man auf jeden Fall neben seinem produktiv genutzten Visual Studio noch eine zweite Express-Version installieren müsste. Da die Installation von mehreren Visual Studio Versionen in manchen Fällen (nicht immer!) zu Problemen führen kann, habe ich mir eine virtuelle Maschine eingerichtet, welche als Entwicklungsplattform dienen sollte. Auf diese habe ich dann
Windows 7 Professional 32 Bit und die
Windows Phone Developer Tools RTW installiert.
Bei ersten Tests fielen mir sofort folgende Nachteile des WP7 Emulators auf :
- Der Emulator ist nicht gerade der schnellste (ich dachte erst, weil er in der virtuellen Maschine läuft, aber die anderen Programme starten fast genauso schnell wie im Host, also liegt es am Emulator)
- GUI-Tests mit vielen Texteingaben sind sehr mühselig, da diese immer über die virtuelle Phone Tastatur erfolgen müssen und nicht über die normale Tastatur erfolgen können
Um die WP7 App also effizient entwickeln zu können, musste ich mir etwas einfallen lassen.
So kam es, dass ich in meinem Host (dem produktiven Entwicklungsrechner) eine Silverlight 4 (SL4) Applikation erstellt habe, welche als Grundlage für die WP7 App dienen sollte.
Um die GUI-Identität zu wahren, musste ich zunächst einmal die optischen Vorgaben einer WP7 App in SL4 abbilden. Hierzu mussten einige Vorarbeiten (das Anlegen von Systemressourcen) geleistet werden.
Folgende Eigenschaften muss das “User Control“ Element besitzen, damit eine WP7 App in SL4 nachgebildet werden kann :
<UserControl … Height="768" Width="480" FontFamily="Segoe UI"
FontSize="20" Foreground="White">
Die Größe des Fenster sollte bei einer „normalen“ Phone 7 Applikation eine Breite von 480px und eine Höhe von 768px haben, bei einer sogenannten „Panorama“ Application liegen die Werte bei einer Breite von 480px und einer Höhe von 800px (in diesem Beitrag wird lediglich die „normale“ Applikation besprochen). Die Nachbildung der drei weiteren Systemressourcen erreicht man mit der Zuweisung der Font-Family „Segoe UI“, einer Font-Size von "20" und der Foreground-Farbe „White“.
Darüber hinaus musste ich die von jeder WP7 App genutzten Font-Systemressourcen in SL4 verfügbar machen. Also habe ich die Font-Systemressourcen in Form von „Application.Resourcen“ nachgebildet :
<Application.Resources>
<!-- Dies sind Nachbildungen der Standard WP7-Systemressourcen -->
<Style x:Key="PhoneTextNormalStyle" TargetType="TextBlock">
<Setter Property="FontFamily" Value="Segoe UI"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontSize" Value="20"/>
</Style>
<Style x:Key="PhoneTextTitle1Style" TargetType="TextBlock">
<Setter Property="FontFamily" Value="Segoe UI Light"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontSize" Value="72"/>
</Style>
<Style x:Key="PhoneTextTitle2Style" TargetType="TextBlock">
<Setter Property="FontFamily" Value="Segoe UI Light"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontSize" Value="32"/>
</Style>
</Application.Resources>
Somit war es mir möglich, diese Ressourcen den TextBlock-Controls zuzuordnen, ohne diese in WP7 nochmals ändern zu müssen (da dort ja identische Systemressourcen existieren) und dennoch eine identische Optik zu erreichen.
Eine in beiden Entwicklungsumgebungen gültige Zuweisung lautet dann :
<TextBlock x:Name="ApplicationTitle" Text="Name der App"
Style="{StaticResource PhoneTextNormalStyle}"/>Wie gesagt behält diese Zuweisung ihre Gültigkeit auch in der WP7 Entwicklungsumgebung und damit hält man auch die
Microsoft GUI Design Standards für die WP7 App Entwicklung ein, welche das Nutzen dieser Systemressourcen ausdrücklich empfehlen.
Nun konnte ich also die WP7 App in SL4
effizient auf meinem schnellen Produktiv-Rechner entwickeln und nicht gezwungenermaßen auf der doch schon etwas langsamerem virtuellen Maschine mit den oben genannten Nachteilen.
Auf diese Weise war es mir möglich die GUI der nachfolgend dargestellten WP7 App voll funktionsfähig innerhalb von nur 5 (!) Tagen inkl. aller dazugehörigen Funktionalitäten wie Datenbindung, DataTemplates, Überblendeffekten, Storyboards, dynamischer Anzeige, Dual-Language-Support etc. fertig zu stellen.
Screenshots der WP7 App :

Das Silverlight-Pendant sieht folgendermaßen aus :


Wie man sieht, sind die optischen Unterschiede nur noch marginal.
Bei Fragen und Anregungen schreibt mir doch einfach einen Kommentar oder eine PN.
Die Vorlage für eine WP7 App in Form eines Silverlight 4 Projektes könnt ihr hier herunterladen :
SL4WP7Template.zip