Archive for the ‘XAML’ Category.
Drag and drop works quite well already with some UIElements, such as TextBox. However, you may want to using MVVM and binding to bind a command for dragging and dropping to a UIElement that doesn’t support drag and drop, such as a Grid or other container.
This can easily be done using an Attached Behavior. You may know what an Attached Property is and you may be wondering, what is the difference between an Attached Behavior and an Attached Property. An Attached Behavior is a type of an Attached Property that involves certain events, often user interaction events such a drag and drop.
So what is the basic recipe for creating an Attached Behavior.
If you are going to handle Drag and Drop in WPF, you need to make yourself acquainted with a particular static object called DataFormats (System.Windows.DataFormats, as there exists one in the Forms namespace too). This object is going to help you. Microsoft says that the DataFormats class provides a set of predefined data format names that can be used to identify data formats available in the clipboard or drag-and-drop operations.[1] You should take a moment to read about this class if you are not familiar with it. IDataObject and DataObject (which implements IDataObject) are also important to know and you should read about those as well.
Determine which types of data your need to handle with Drag and Drop. For each data type you plan to handle, you should check if the drag and drop data is that type and if so, handle it. You have the option to handle this in the code behind or in the behavior.
Also, with UIElements in WPF, the drag events are separate from the drop events, so we really don’t need a DragBehavior or DragAndDropBehavior, instead we only need a DropBehavior.[2] You should read about the drag and drop events and understand which one you need to add a behavior too. We are going to use the PreviewDrop event.
Ok, now that we have gained the knowledge we need, lets write a DropBavior static class.
using System.Windows.Input; using System.Windows; namespace MVVM { /// <summary> /// This is an Attached Behavior and is intended for use with /// XAML objects to enable binding a drag and drop event to /// an ICommand. /// </summary> public static class DropBehavior { #region The dependecy Property /// <summary> /// The Dependency property. To allow for Binding, a dependency /// property must be used. /// </summary> private static readonly DependencyProperty PreviewDropCommandProperty = DependencyProperty.RegisterAttached ( "PreviewDropCommand", typeof(ICommand), typeof(DropBehavior), new PropertyMetadata(PreviewDropCommandPropertyChangedCallBack) ); #endregion #region The getter and setter /// <summary> /// The setter. This sets the value of the PreviewDropCommandProperty /// Dependency Property. It is expected that you use this only in XAML /// /// This appears in XAML with the "Set" stripped off. /// XAML usage: /// /// <Grid mvvm:DropBehavior.PreviewDropCommand="{Binding DropCommand}" /> /// /// </summary> /// <param name="inUIElement">A UIElement object. In XAML this is automatically passed /// in, so you don't have to enter anything in XAML.</param> /// <param name="inCommand">An object that implements ICommand.</param> public static void SetPreviewDropCommand(this UIElement inUIElement, ICommand inCommand) { inUIElement.SetValue(PreviewDropCommandProperty, inCommand); } /// <summary> /// Gets the PreviewDropCommand assigned to the PreviewDropCommandProperty /// DependencyProperty. As this is only needed by this class, it is private. /// </summary> /// <param name="inUIElement">A UIElement object.</param> /// <returns>An object that implements ICommand.</returns> private static ICommand GetPreviewDropCommand(UIElement inUIElement) { return (ICommand)inUIElement.GetValue(PreviewDropCommandProperty); } #endregion #region The PropertyChangedCallBack method /// <summary> /// The OnCommandChanged method. This event handles the initial binding and future /// binding changes to the bound ICommand /// </summary> /// <param name="inDependencyObject">A DependencyObject</param> /// <param name="inEventArgs">A DependencyPropertyChangedEventArgs object.</param> private static void PreviewDropCommandPropertyChangedCallBack( DependencyObject inDependencyObject, DependencyPropertyChangedEventArgs inEventArgs) { UIElement uiElement = inDependencyObject as UIElement; if (null == uiElement) return; uiElement.Drop += (sender, args) => { GetPreviewDropCommand(uiElement).Execute(args.Data); args.Handled = true; }; } #endregion } }
Ok, now it is really easy to add this to a Grid or other UIElement that doesn’t already handle Drag and Drop. If you try to add this to a TextBlox, which already handles Drag and Drop, this even doesn’t fire.
You can add the DropBehavior to a UserControl
<UserControl x:Class="DropBehaviorExample.ExampleView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:MVVM="clr-namespace:MVVM" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400" AllowDrop="True" MVVM:DropBehavior.PreviewDropCommand="{Binding PreviewDropCommand}" > <Grid> </Grid> </UserControl>
Or you can add the DropBehavior to a Grid.
<UserControl x:Class="DropBehaviorExample.ExampleView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:MVVM="clr-namespace:MVVM" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400" AllowDrop="True" > <Grid MVVM:DropBehavior.PreviewDropCommand="{Binding PreviewDropCommand}"> </Grid> </UserControl>
And of course, you can put it on any UIElement that doesn’t already handle drag and drop.
The following code is all you need in your ViewModel to bind the PreviewDropCommand.
#region The RelayCommand that implements ICommand public ICommand PreviewDropCommand { get { return _PreviewDropCommand ?? (_PreviewDropCommand = new RelayCommand(HandlePreviewDrop)); } set { _PreviewDropCommand = value; NotifyPropertyChanged("PreviewDropCommand"); } } private ICommand _PreviewDropCommand; #endregion #region The method encapsulated in the relay command private void HandlePreviewDrop(object inObject) { IDataObject ido = inObject as IDataObject; if (null == ido) return; // Get all the possible format string[] formats = ido.GetFormats(); // Do what you need here based on the format passed in. // You will probably have a few options and you need to // decide an order of preference. } #endregion
Hope this helps you. I know when I first rounded up this information online, it was hard to understand because of lack of preparation information, so I made sure to provide that, and also lack of comments, so I made sure to provide that.
I wanted to highlight some of the similarities and some of the differences of ListBox vs ItemsControl.
These two controls are similar in a few ways.
ItemsControl uses an ItemsPresenter to present its children.
System.Object
System.Windows.Threading.DispatcherObject
System.Windows.DependencyObject
System.Windows.Media.Visual
System.Windows.UIElement
System.Windows.FrameworkElement
System.Windows.Controls.Control
System.Windows.Controls.ItemsControl
A ListBox is an ItemsControl but an ItemsControl is not a list box. Notice a list box is a descendant of ItemsControl but has a Selector object, which is abstract, that it specifically derives from. Inheriting from Selector provides ListBox with selection events and selection features that are missing from ItemsControl. ListBox adds a SelectionMode Dependency Property that allows for selection types of Single, Multiple, or Extended, which are defined in a SelectionMode enum. It uses ListBoxItem as its default child.
…
System.Windows.Controls.ItemsControl
System.Windows.Controls.Primitives.Selector
System.Windows.Controls.ListBox
ListView inherits from ListBox, so it is everything a ListBox is plus it has a View Dependency Property and some functions supporting the View Dependency Property. It uses ListViewItem as its default child.
…
System.Windows.Controls.ItemsControl
System.Windows.Controls.Primitives.Selector
System.Windows.Controls.ListBox
System.Windows.Controls.ListView
Note: These default styles are obtained using Expresion Blend by right-clicking a Control and choosing Edit Template | Edit a copy.
This style is short and simple.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Style x:Key="ItemsControlDefaultStyle" TargetType="{x:Type ItemsControl}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ItemsControl}"> <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true"> <ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style> <!-- Resource dictionary entries should be defined here. --> </ResourceDictionary>
ListBox and ListView have the same default style, with the only exception being the the TargetType, which is of course ListBox or ListView repectively.
Both have all the elements ItemsControl has, but also there are more Setters as Setters exist for the ScrollViewer and for the Border. ListBox and ListView styles also include a ScrollViewer in the ControlTemplate between the Border and the ItemsPresenter. ListBox and ListView also have a ControlTemplate.Triggers section.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <SolidColorBrush x:Key="ListBorder" Color="#828790"/> <Style x:Key="ListBoxDefaultStyle" TargetType="{x:Type ListBox}"> <Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/> <Setter Property="BorderBrush" Value="{StaticResource ListBorder}"/> <Setter Property="BorderThickness" Value="1"/> <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/> <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/> <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/> <Setter Property="ScrollViewer.CanContentScroll" Value="true"/> <Setter Property="ScrollViewer.PanningMode" Value="Both"/> <Setter Property="Stylus.IsFlicksEnabled" Value="False"/> <Setter Property="VerticalContentAlignment" Value="Center"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ListBox}"> <Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="1" SnapsToDevicePixels="true"> <ScrollViewer Focusable="false" Padding="{TemplateBinding Padding}"> <ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/> </ScrollViewer> </Border> <ControlTemplate.Triggers> <Trigger Property="IsEnabled" Value="false"> <Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/> </Trigger> <Trigger Property="IsGrouping" Value="true"> <Setter Property="ScrollViewer.CanContentScroll" Value="false"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> <!-- Resource dictionary entries should be defined here. --> </ResourceDictionary>
Note: If you accidentally put ListBoxItems under ListView, the items are wrapped in a ListViewItem control which has its own margin, which will make the items move 5 pixels further to the right.
It is pretty easy to choose between ItemsControl and either ListBox or ListView. If you do not want the ability to select a row or switch the View, use ItemsControl. Otherwise, use ListBox or ListView.
Also, if you need the select feature but you do not need to be able to switch the View, use ListBox.
If you need to be able to switch the View, then choose ListView. It doesn’t matter if you need the ability to select or not.
Lets say you want to create a submittable form in WPF. You want to make sure the form is filled out with valid values and you want the Submit button to be disabled until the form is filled out with valid values. Of course, the error text should display on error only.
See the picture on the right.
This is quite simple. Here is a sample project that demonstrates this: WpfTextBoxValidation.zip
Accomplishing this is done by using a few different tools available:
So the code is so simple, I am going to skip the step by step instructions for time reasons. However, the code is pretty self documenting as the object names are obvious and the UI objects are obvious, at least when viewed in the Designer.
<Window x:Class="WpfTextBoxValidation.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WpfTextBoxValidation" Title="MainWindow" Height="350" Width="525"> <Grid Name="MainGrid" Margin="10"> <Grid.Resources> <Style TargetType="TextBox"> <Setter Property="MaxWidth" Value="200" /> </Style> </Grid.Resources> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" MinWidth="200" /> <ColumnDefinition Width="237*" /> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="129*" /> </Grid.RowDefinitions> <!-- Labels --> <TextBlock Name="labelFirstName" Text="FirstName" Margin="5" /> <TextBlock Name="labelLastName" Text="LastName" Grid.Row="1" Margin="5" /> <TextBlock Text="Age" Grid.Row="2" Margin="5" /> <TextBlock Text="Phone" Grid.Row="3" Margin="5" /> <!-- TextBlocks --> <TextBox Name="TextBoxFirstName" Grid.Column="1" Margin="5"> <TextBox.Text> <Binding Path="FirstName" UpdateSourceTrigger="PropertyChanged" > <Binding.ValidationRules> <local:TextBoxNotEmptyValidationRule x:Name="FirstNameValidation" ValidatesOnTargetUpdated="True" Message="You must enter a first name."/> </Binding.ValidationRules> </Binding> </TextBox.Text> </TextBox> <TextBox Name="TextBoxLastName" Grid.Row="1" Grid.Column="1" Margin="5"> <TextBox.Text> <Binding Path="LastName" UpdateSourceTrigger="PropertyChanged" > <Binding.ValidationRules> <local:TextBoxNotEmptyValidationRule x:Name="LastNameValidation" ValidatesOnTargetUpdated="True" Message="You must enter a last name."/> </Binding.ValidationRules> </Binding> </TextBox.Text> </TextBox> <TextBox Name="TextBoxAge" Grid.Row="2" Grid.Column="1" Margin="5"> <TextBox.Text> <Binding Path="Age" UpdateSourceTrigger="PropertyChanged" > <Binding.ValidationRules> <local:OverThirteenValidationRule x:Name="AgeValidation" ValidatesOnTargetUpdated="True"/> </Binding.ValidationRules> </Binding> </TextBox.Text> </TextBox> <TextBox Name="TextBoxPhone" Grid.Row="3" Grid.Column="1" Margin="5"> <TextBox.Text> <Binding Path="Phone" UpdateSourceTrigger="PropertyChanged" > <Binding.ValidationRules> <local:TextBoxNotEmptyValidationRule x:Name="PhoneValidation" ValidatesOnTargetUpdated="True" Message="You must enter a phone number."/> </Binding.ValidationRules> </Binding> </TextBox.Text> </TextBox> <!-- Validation List --> <StackPanel Grid.Row="4" Grid.ColumnSpan="2" Margin="5"> <StackPanel.Resources> <Style TargetType="TextBlock"> <Setter Property="Foreground" Value="Red" /> </Style> <local:ErrorCollectionToVisibility x:Key="ToVisibility" /> </StackPanel.Resources> <TextBlock Visibility="{Binding ElementName=TextBoxFirstName, Path=(Validation.Errors), Converter={StaticResource ToVisibility}}"> <TextBlock.Text> <MultiBinding StringFormat="{}{0} - {1}"> <Binding ElementName="labelFirstName" Path="Text"/> <Binding ElementName="TextBoxFirstName" Path="(Validation.Errors)[0].ErrorContent"/> </MultiBinding> </TextBlock.Text> </TextBlock> <TextBlock Visibility="{Binding ElementName=TextBoxLastName, Path=(Validation.Errors), Converter={StaticResource ToVisibility}}">> <TextBlock.Text> <MultiBinding StringFormat="LastName - {0}"> <Binding ElementName="TextBoxLastName" Path="(Validation.Errors)[0].ErrorContent"/> </MultiBinding> </TextBlock.Text> </TextBlock> <TextBlock Visibility="{Binding ElementName=TextBoxAge, Path=(Validation.Errors), Converter={StaticResource ToVisibility}}">> <TextBlock.Text> <MultiBinding StringFormat="Age - {0}"> <Binding ElementName="TextBoxAge" Path="(Validation.Errors)[0].ErrorContent"/> </MultiBinding> </TextBlock.Text> </TextBlock> <TextBlock Visibility="{Binding ElementName=TextBoxPhone, Path=(Validation.Errors), Converter={StaticResource ToVisibility}}">> <TextBlock.Text> <MultiBinding StringFormat="Phone - {0}"> <Binding ElementName="TextBoxPhone" Path="(Validation.Errors)[0].ErrorContent"/> </MultiBinding> </TextBlock.Text> </TextBlock> <!--Text="{Binding ElementName=DirectoryBox, Path=(Validation.Errors)[0].ErrorContent}"--> </StackPanel> <Button Content="Submit" Grid.Column="1" Grid.Row="5" Margin="5" Padding="10,3,10,3" HorizontalAlignment="Right" Name="buttonSubmit" VerticalAlignment="Top"> <Button.Style> <Style TargetType="{x:Type Button}"> <Setter Property="IsEnabled" Value="false" /> <Style.Triggers> <MultiDataTrigger> <MultiDataTrigger.Conditions> <Condition Binding="{Binding ElementName=TextBoxFirstName, Path=(Validation.HasError)}" Value="false" /> <Condition Binding="{Binding ElementName=TextBoxLastName, Path=(Validation.HasError)}" Value="false" /> <Condition Binding="{Binding ElementName=TextBoxAge, Path=(Validation.HasError)}" Value="false" /> <Condition Binding="{Binding ElementName=TextBoxPhone, Path=(Validation.HasError)}" Value="false" /> </MultiDataTrigger.Conditions> <Setter Property="IsEnabled" Value="true" /> </MultiDataTrigger> </Style.Triggers> </Style> </Button.Style> </Button> </Grid> </Window>
Now, lets look at the supporting objects:
using System.Windows; namespace WpfTextBoxValidation { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); Person p = new Person(); //PersonViewModel pvm = new PersonViewModel() { Person = p }; MainGrid.DataContext = p; } } }
In The above class, I add an instance of the Person object and make that the DataContext.
using System; namespace WpfTextBoxValidation { public class Person { public String FirstName { get; set; } public String LastName { get; set; } public String Age { get; set; } public String Phone { get; set; } } }
The above class is a simple standard person object for examples.
using System; using System.Windows.Controls; namespace WpfTextBoxValidation { public class OverThirteenValidationRule : ValidationRule { public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo) { if (value != null) { int age = 0; try { age = Convert.ToInt32(value); } catch { return new ValidationResult(false, "You must be older than 13!"); } if (age > 13) return ValidationResult.ValidResult; } return new ValidationResult(false, "You must be older than 13!"); } } }
The above class is an example ValidationRule that requires the person be 13 or older.
using System; using System.Windows.Controls; namespace WpfTextBoxValidation { public class TextBoxNotEmptyValidationRule : ValidationRule { public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo) { string str = value as string; if (str != null) { if (str.Length > 0) return ValidationResult.ValidResult; } return new ValidationResult(false, Message); } public String Message { get; set; } } }
The above class is an example ValidationRule that makes sure a TextBox is not empty.
using System; using System.Collections.ObjectModel; using System.Windows; using System.Windows.Controls; using System.Windows.Data; namespace WpfTextBoxValidation { class ErrorCollectionToVisibility : IValueConverter { public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { var collection = value as ReadOnlyCollection<ValidationError>; if (collection != null && collection.Count > 0) return Visibility.Visible; else return Visibility.Collapsed; } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { return new object(); } } }
In the above class, I am using a converter to change to Visibility.Visible if there are no errors or change to Visibility.Collapsed if there is one or more errors.
With this simple project you should be able to now have a form in WPF with ValidationRules and disable the submit button should there be an ValidationErrors.
I am including my ResourceDictionary files as Content files. Content files means they are not compiled or embedded, but they are loose files on disk.
One issue I need to solve was how to have the Design View in Expression Blend or Visual Studio.
It seems that Expression Blend has a partial solution but it doesn’t work in Visual Studio for me.
<Content Include="Resources\en-US\Common.View.LocalizationResources.en-US.xaml"> <Generator>MSBuild:Compile</Generator> <SubType>Designer</SubType> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content>
<Content Include="Resources\en-US\Common.View.LocalizationResources.en-US.xaml" Condition="'$(DesignTime)'=='true' OR ('$(SolutionPath)'!='' AND Exists('$(SolutionPath)') AND '$(BuildingInsideVisualStudio)'!='true' AND '$(BuildingInsideExpressionBlend)'!='true')"> <Generator>MSBuild:Compile</Generator> <SubType>Designer</SubType> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content>
Sometimes Expression Blend will prompt you when it can’t find a resource and it will do this for you. However, it does something interesting.
It adds a file called DesignTimeResources.xaml in the Properties folder as a Page (not as Content).
<Page Include="Properties\DesignTimeResources.xaml" Condition="'$(DesignTime)'=='true' OR ('$(SolutionPath)'!='' AND Exists('$(SolutionPath)') AND '$(BuildingInsideVisualStudio)'!='true' AND '$(BuildingInsideExpressionBlend)'!='true')"> <Generator>MSBuild:Compile</Generator> <SubType>Designer</SubType> <ContainsDesignTimeResources>true</ContainsDesignTimeResources> </Page>
The file then looks as follows:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="E:\Dev\LD\Trunk\install\Common\LANDesk.Install.Common\Resources\en-US\Common.LocalizationResources.en-US.xaml"/> </ResourceDictionary.MergedDictionaries> <!-- Resource dictionary entries should be defined here. --> </ResourceDictionary>
And then you can add more ResourceDictionaries without modifying the .proj file.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="E:\Dev\LD\Trunk\install\SomeApp\Branding\StyleResources.xaml"/> <ResourceDictionary Source="E:\Dev\LD\Trunk\install\SomeApp\Branding\AlternateStyleResources.xaml"/> <ResourceDictionary Source="E:\Dev\LD\Trunk\install\SomeApp\Resources\en-US\en-US.xaml"/> </ResourceDictionary.MergedDictionaries> <!-- Resource dictionary entries should be defined here. --> </ResourceDictionary>
Of course relative paths are more desired than static paths, especially when using projects among multiple people and from source control.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="..\Branding\StyleResources.xaml"/> <ResourceDictionary Source="..\Branding\AlternateStyleResources.xaml"/> <ResourceDictionary Source="..\Resources\en-US\en-US.xaml"/> </ResourceDictionary.MergedDictionaries> <!-- Resource dictionary entries should be defined here. --> </ResourceDictionary>
Resources:
http://www.tweened.org/en/2010/06/07/design-time-resources-dans-blend-4/
Update: I created a project for this. It also has a NuGet package.
See https://github.com/rhyous/WPFSharp.Globalizer.
Dynamically changing the language of a WPF application at run-time or on-the-fly is possible and quite simple.
With this solution you do not have Resources.resx files, you do not have to have x:Uid on all your XAML tags. See the expectations this solution is following here: My WPF Localization and Language Expectations. Most of my expectations are met with this solution.
Here is the sample project code: How to load a DictionaryStyle.xaml file at run time?
The App.xaml.cs is empty by default. We are going to add a few variables, a constructor, and add a few simple functions. This is a fairly small amount of code.
using System; using System.Globalization; using System.IO; using System.Threading; using System.Windows; namespace WpfRuntimeLocalizationExample { /// <summary> /// Interaction logic for App.xaml /// </summary> public partial class App : Application { #region Member variables public static App Instance; public static String Directory; public event EventHandler LanguageChangedEvent; #endregion #region Constructor public App() { // Initialize static variables Instance = this; Directory = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location); // Load the Localization Resource Dictionary based on OS language SetLanguageResourceDictionary(GetLocXAMLFilePath(CultureInfo.CurrentCulture.Name)); } #endregion #region Functions /// <summary> /// Dynamically load a Localization ResourceDictionary from a file /// </summary> public void SwitchLanguage(string inFiveCharLang) { if (CultureInfo.CurrentCulture.Name.Equals(inFiveCharLang)) return; var ci = new CultureInfo(inFiveCharLang); Thread.CurrentThread.CurrentCulture = ci; Thread.CurrentThread.CurrentUICulture = ci; SetLanguageResourceDictionary(GetLocXAMLFilePath(inFiveCharLang)); if (null != LanguageChangedEvent) { LanguageChangedEvent(this, new EventArgs()); } } /// <summary> /// Returns the path to the ResourceDictionary file based on the language character string. /// </summary> /// <param name="inFiveCharLang"></param> /// <returns></returns> private string GetLocXAMLFilePath(string inFiveCharLang) { string locXamlFile = "LocalizationDictionary." + inFiveCharLang + ".xaml"; return Path.Combine(Directory, inFiveCharLang, locXamlFile); } /// <summary> /// Sets or replaces the ResourceDictionary by dynamically loading /// a Localization ResourceDictionary from the file path passed in. /// </summary> /// <param name="inFile"></param> private void SetLanguageResourceDictionary(String inFile) { if (File.Exists(inFile)) { // Read in ResourceDictionary File var languageDictionary = new ResourceDictionary(); languageDictionary.Source = new Uri(inFile); // Remove any previous Localization dictionaries loaded int langDictId = -1; for (int i = 0; i < Resources.MergedDictionaries.Count; i++) { var md = Resources.MergedDictionaries[i]; // Make sure your Localization ResourceDictionarys have the ResourceDictionaryName // key and that it is set to a value starting with "Loc-". if (md.Contains("ResourceDictionaryName")) { if (md["ResourceDictionaryName"].ToString().StartsWith("Loc-")) { langDictId = i; break; } } } if (langDictId == -1) { // Add in newly loaded Resource Dictionary Resources.MergedDictionaries.Add(languageDictionary); } else { // Replace the current langage dictionary with the new one Resources.MergedDictionaries[langDictId] = languageDictionary; } } } #endregion } }
We need a little sample project to demonstrate the localization, so lets quickly make one.
<Window x:Class="WpfRuntimeLocalizationExample.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="WPF Run-time Localization Example" MinHeight="350" MinWidth="525"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="30" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="25" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="15" /> <ColumnDefinition Width="*" MinWidth="100"/> <ColumnDefinition Width="*" MinWidth="200"/> <ColumnDefinition Width="15" /> </Grid.ColumnDefinitions> <DockPanel Grid.ColumnSpan="4" > <Menu DockPanel.Dock="Top" > <MenuItem Header="_File"> <MenuItem Header="E_xit" Click="MenuItem_Exit_Click" /> </MenuItem> <MenuItem Name="menuItemLanguages" Header="_Languages"> <MenuItem Tag="en-US" Header="_English" Click="MenuItem_Style_Click" /> <MenuItem Tag="es-ES" Header="_Spanish" Click="MenuItem_Style_Click" /> <MenuItem Tag="he-IL" Header="_Hebrew" Click="MenuItem_Style_Click" /> </MenuItem> </Menu> </DockPanel> <Label Content="First Name" Name="labelFirstName" Grid.Row="2" FlowDirection="RightToLeft" Grid.Column="1" /> <Label Content="Last Name" Name="labelLastName" Grid.Row="4" FlowDirection="RightToLeft" Grid.Column="1" /> <Label Content="Age" Name="labelAge" Grid.Row="3" FlowDirection="RightToLeft" Grid.Column="1" /> <TextBox Name="textBox1" Grid.Column="2" Grid.Row="2" /> <TextBox Name="textBox2" Grid.Column="2" Grid.Row="3" /> <TextBox Name="textBox3" Grid.Column="2" Grid.Row="4" /> <Button Content="Clear" Grid.Column="2" Grid.Row="5" Height="23" HorizontalAlignment="Right" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" /> </Grid> </Window>
using System; using System.Globalization; using System.Windows; using System.Windows.Controls; namespace WpfRuntimeLocalizationExample { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); foreach (MenuItem item in menuItemLanguages.Items) { if (item.Tag.ToString().Equals(CultureInfo.CurrentUICulture.Name)) item.IsChecked = true; } } private void MenuItem_Exit_Click(object sender, RoutedEventArgs e) { Environment.Exit(0); } private void MenuItem_Style_Click(object sender, RoutedEventArgs e) { // Uncheck each item foreach (MenuItem item in menuItemLanguages.Items) { item.IsChecked = false; } MenuItem mi = sender as MenuItem; mi.IsChecked = true; App.Instance.SwitchLanguage(mi.Tag.ToString()); } private void button1_Click(object sender, RoutedEventArgs e) { labelFirstName.Content = labelLastName.Content = labelAge.Content = string.Empty; } } }
Though localization is not yet working, this should compile and run.
By recommendation of my expert localization team, we are going to create a folder for each language, using the five character string (en-US, es-ES, he-IL), and put a ResourceDictionary in each folder. The resource dictionary will also have the five character language string.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:sys="clr-namespace:System;assembly=mscorlib"> <!-- The name of this ResourceDictionary. Should not be localized. --> <sys:String x:Key="ResourceDictionaryName" Localization.Comments="$Content(DoNotLocalize)">Loc-en-US</sys:String> </ResourceDictionary>
<Window x:Class="WpfRuntimeLocalizationExample.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="{DynamicResource ResourceKey=MainWindow_Title}" MinHeight="350" MinWidth="525"> <Grid>
<!-- Localized strings --> <sys:String x:Key="MainWindow_Title">WPF Run-time Localization Example</sys:String>
The main motivation for adding Hebrew to this example is so that we can show how to dynamically switch FlowDirection for languages that flow from right to left.
Note: This step is optional and only necessary if your application will support a language that flows right to left.
<!-- Localization specific styles --> <FlowDirection x:Key="FlowDirection_Default" Localization.Comments="$Content(DoNotLocalize)">LeftToRight</FlowDirection> <FlowDirection x:Key="FlowDirection_Reverse" Localization.Comments="$Content(DoNotLocalize)">RightToLeft</FlowDirection>
<!-- Localization specific styles --> <FlowDirection x:Key="FlowDirection_Default" Localization.Comments="$Content(DoNotLocalize)">RightToLeft</FlowDirection> <FlowDirection x:Key="FlowDirection_Reverse" Localization.Comments="$Content(DoNotLocalize)">LeftToRight</FlowDirection>
<Window x:Class="WpfRuntimeLocalizationExample.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="{DynamicResource MainWindow_Title}" MinHeight="350" MinWidth="525" FlowDirection="{DynamicResource FlowDirection_Default}"> <Grid>
Build compile and test. Both the language and FlowDirection should now change when switching to Hebrew.
https://github.com/rhyous/WPFSharp.Globalizer
WPF Localization and Language switching at run-time is not exactly solved clearly by Microsoft. After my experience (which includes researching for days on separate occasions and changing to localize using Resources.resx when localizing with BAML was not working for everyone involved) I had come to the conclusion that WPF Localization was just going to remain a black mark on WPF until Microsoft provided an easy recommended solution.
Microsoft has a lot of information on localization. They have pages and pages of it and a lot of talks about localization using Resources.resx or using BAML (Binary XAML).
I came to realize that I had the following expectations:
Neither the Resources.resx or the XAML solutions seemed to meet my expectations.
Time came to decide on a localization method for WPF again, and I again embarked into similar research as before. I found all the same answers.
However, I am now a WPF Expert. A Senior WPF Developer if you can call anyone such for such a young technology.
I went through all the same pages as before only this time, I pieced together a localization solution that comes the closest to meeting all my expectations.
If you don’t agree with most of my expectations, feel free to use another WPF localization solution. I am not going to argue that mine is the best for everyone, just the best for me and my expectations.
In all my searching I have found no one who is using my solution. Expect my new solution soon and link at the end of this post soon.
Changing a theme or style at run-time is possible by assigning styles using DynamicResource binding and loading a ResourceDictionary (XAML) from a Content file at run-time.
This can be done by loading a DictionaryStyle.xaml at run time.
If you would prefer to just jump right in, download the source code: RuntimeLoadOfResourceDictionaryExample.zip
<Window x:Class="RuntimeLoadOfResourceDictionaryExample.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Style="{DynamicResource ResourceKey=MainWindowStyle}" > <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="30" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="25" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="15" /> <ColumnDefinition Width="*" MinWidth="100"/> <ColumnDefinition Width="*" MinWidth="200"/> <ColumnDefinition Width="15" /> </Grid.ColumnDefinitions> <DockPanel Grid.ColumnSpan="4" > <Menu DockPanel.Dock="Top" > <MenuItem Header="_File"> <MenuItem Header="E_xit" Click="MenuItem_Exit_Click" /> </MenuItem> <MenuItem Header="_Styles"> <MenuItem Name="BlueStyle" Header="_Blue" Click="MenuItem_Style_Click" /> <MenuItem Name="GreyStyle" Header="_Grey" Click="MenuItem_Style_Click" /> </MenuItem> </Menu> </DockPanel> <Label Content="First Name" Name="label1" Grid.Row="2" FlowDirection="RightToLeft" Grid.Column="1" /> <Label Content="Age" Name="label3" Grid.Row="3" FlowDirection="RightToLeft" Grid.Column="1" /> <Label Content="Last Name" Name="label2" Grid.Row="4" FlowDirection="RightToLeft" Grid.Column="1" /> <TextBox Name="textBox1" Grid.Column="2" Grid.Row="2" /> <TextBox Name="textBox2" Grid.Column="2" Grid.Row="3" /> <TextBox Name="textBox3" Grid.Column="2" Grid.Row="4" /> <Button Content="Clear" Grid.Column="2" Grid.Row="5" Height="23" HorizontalAlignment="Right" Name="button1" VerticalAlignment="Top" Width="75" /> </Grid> </Window>
private void MenuItem_Exit_Click(object sender, RoutedEventArgs e) { Environment.Exit(0); } private void MenuItem_Style_Click(object sender, RoutedEventArgs e) { MenuItem mi = sender as MenuItem; string stylefile = Path.Combine(App.Directory, "Styles", mi.Name + ".xaml"); App.Instance.LoadStyleDictionaryFromFile(stylefile); }
It won’t compile just yet as some of the code doesn’t exist yet.
This is going to take place in the App.xaml.cs file. App.xaml.cs is the ultimate parent and by loading the ResourceDictionary here, every child will have access to them. This especially becomes important with larger projects where WPF UI elements, controls, windows, or others may come from other dlls.
using System; using System.IO; using System.Windows; using System.Windows.Markup; namespace RuntimeLoadOfResourceDictionaryExample { /// <summary> /// Interaction logic for App.xaml /// </summary> public partial class App : Application { public static App Instance; public static String Directory; private String _DefaultStyle = "BlueStyle.xaml"; public App() { Instance = this; Directory = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location); string stringsFile = Path.Combine(Directory, "Styles", _DefaultStyle); LoadStyleDictionaryFromFile(stringsFile); } /// <summary> /// This funtion loads a ResourceDictionary from a file at runtime /// </summary> public void LoadStyleDictionaryFromFile(string inFileName) { if (File.Exists(inFileName)) { try { using (var fs = new FileStream(inFileName, FileMode.Open, FileAccess.Read, FileShare.Read)) { // Read in ResourceDictionary File var dic = (ResourceDictionary)XamlReader.Load(fs); // Clear any previous dictionaries loaded Resources.MergedDictionaries.Clear(); // Add in newly loaded Resource Dictionary Resources.MergedDictionaries.Add(dic); } } catch { } } } } }
Now, we haven’t create the default style yet, don’t fret, that is the next step.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Style x:Key="MainWindowStyle" TargetType="Window"> <Setter Property="SizeToContent" Value="WidthAndHeight" /> <Setter Property="Background" Value="RoyalBlue" /> </Style> <Style TargetType="Label"> <Setter Property="Margin" Value="0,5,0,5" /> </Style> <Style TargetType="TextBox"> <Setter Property="Margin" Value="0,5,0,5" /> </Style> </ResourceDictionary>
Your project should now be working. You should be able to build and run it. You should be able to click on Styles and change the style.
Many people want to include an image but for reasons such as the ability to change branding, the image should not be compiled in.
For most, you don’t need steps to create a project, but just in case you are creating a WPF project for the first time, here are the steps.
Were are going to create a folder called images and put the image there.
<Window x:Class="RelativePathImages.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" SizeToContent="WidthAndHeight" MinHeight="200" MinWidth="300"> <Grid> <Image /> </Grid> </Window>
<Grid> <Image Source="pack://siteoforigin:,,,/Images/Sword.png" MaxWidth="1024"/> </Grid>
Note: I also added a MaxWidth to the image because the resolution was huge.
Your image is now loaded from whatever image is located in the relative path from your executable. Note: Another option for doing this includes using a ViewModel and binding.
You can configure the access modifier of objects defined in XAML using x:FieldModifier.
<TextBox Name="MyInternalTextBox" x:FieldModifier="internal" /> <TextBox Name="MyPrivateTextBox" x:FieldModifier="private" /> <TextBox Name="MyPrivateTextBox" x:FieldModifier="protected" /> <TextBox Name="MyPublicTextBox" x:FieldModifier="public" />