Often your projects is created with default references, some of which you will never use. Sometime you add a reference but then end up not using it. Over time you may have over a dozen references in each of a dozen projects and you know you are not using half of them.
I know two tools that will resolve this. One if free and is a plugin only for this feature. The other is a commercial product and has hundred of features.
I prefer the free tool by far as it is a simple and easy to use Visual Studio plugin. So I will test it first.
Click the link and it download and installs in seconds.
Then you will see the feature when you right-click on a project in Visual Studio.
Click it and it will remove any references that are not actually being used.
Ok, now lets create a sample project and see if this will work.
I am created a new Console Application and added the following code to the Program.cs
using System; using System.Data; namespace TooManyReferences { class Program { static void Main(string[] args) { // I need System, System.Data, and System.Xml DataTable table = new DataTable(); table.Columns.Add(new DataColumn("FistName",typeof(String))); table.Columns.Add(new DataColumn("LastName", typeof(String))); DataRow row = table.NewRow(); row["FirstName"] = "Jared"; row["LastName"] = "Barneck"; table.Rows.Add(row); Console.WriteLine(table.Rows[0]); } } }
Now lets right-click on the project and choose Remove Unused References.
Unfortunately, my expectation were not met.
The following reference should remain:
My project should build.
The following references remained:
My project failed to build with the following errors
Error 1 The type 'System.Xml.Serialization.IXmlSerializable' is defined in an assembly that is not referenced. You must add a reference to assembly 'System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. C:\Users\212070783\Documents\Visual Studio 2010\Projects\TooManyReferences\TooManyReferences\Program.cs 11 13 TooManyReferences Error 2 The type 'System.ComponentModel.ISupportInitialize' is defined in an assembly that is not referenced. You must add a reference to assembly 'System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. C:\Users\212070783\Documents\Visual Studio 2010\Projects\TooManyReferences\TooManyReferences\Program.cs 11 13 TooManyReferences Error 3 The type 'System.ComponentModel.ISupportInitializeNotification' is defined in an assembly that is not referenced. You must add a reference to assembly 'System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. C:\Users\212070783\Documents\Visual Studio 2010\Projects\TooManyReferences\TooManyReferences\Program.cs 11 13 TooManyReferences Error 4 The type 'System.ComponentModel.IListSource' is defined in an assembly that is not referenced. You must add a reference to assembly 'System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. C:\Users\212070783\Documents\Visual Studio 2010\Projects\TooManyReferences\TooManyReferences\Program.cs 11 13 TooManyReferences Error 5 The type 'System.ComponentModel.MarshalByValueComponent' is defined in an assembly that is not referenced. You must add a reference to assembly 'System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. C:\Users\212070783\Documents\Visual Studio 2010\Projects\TooManyReferences\TooManyReferences\Program.cs 11 13 TooManyReferences
Note: Remember TooManyReferences is the name of my sample project and that is actually confusing in the log.
This tool removes references it should not remove.
If you use this tool, try using it on one project at a time in a large solution, otherwise you could get overwhelmed with failures.
It is pretty easy to add back references, so it is usable, but if it could be taken one step further, to resolve the references needed by objects my code uses, then it would probably be the best and simplest tool out there for this.
Bug Submitted
Resharper is the commercial tool that solves this problem. Unfortunately it is not free. However, it works well and I don’t currently have a license, but remember that it did not remove needed references when I last used it. Here is a link for more information.
http://www.jetbrains.com/resharper/webhelp/Refactorings__Remove_Unused_References.html
WPF in Visual Studio Training Videos
WPF – Design, Layout, Styles, T
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.
WPF supports the ability to make an image load immediately as a splash screen to give your application the appearance of loading faster. It doesn’t actually load faster, but seeing the splash screen is a “feel good” experience for the user.
Adding a Splash Screen to a WPF application is very easy, now.
Yes it is that easy. You are done.
We found that on some machines the splash screen is a “must have” because the application load time was greater than two seconds. In fact, it was so long that without the splash screen, the user thought the program didn’t start and clicked it again. So a splash screen was added the default way.
However, on newer and faster machines, the application load time was less than half a second. For these newer and faster devices, the splash screen loads and closes so quickly it is awkward.
It would actually be better to always have the splash screen show for a minimum of two seconds whether the machine is fast or slow.
<Application x:Class="SimpleSplashExample.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" StartupUri="MainWindow.xaml"> <Application.Resources> </Application.Resources> </Application>
<Application x:Class="SimpleSplashExample.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" > <Application.Resources> </Application.Resources> </Application>
using System.Windows; namespace SimpleSplashExample { /// <summary> /// Interaction logic for App.xaml /// </summary> public partial class App : Application { } }
using System; using System.Diagnostics; using System.Threading; using System.Windows; namespace SimpleSplashExample { /// <summary> /// Interaction logic for App.xaml /// </summary> public partial class App : Application { private const int MINIMUM_SPLASH_TIME = 1500; // Miliseconds private const int SPLASH_FADE_TIME = 500; // Miliseconds protected override void OnStartup(StartupEventArgs e) { // Step 1 - Load the splash screen SplashScreen splash = new SplashScreen("Images/Globe-256.png"); splash.Show(false, true); // Step 2 - Start a stop watch Stopwatch timer = new Stopwatch(); timer.Start(); // Step 3 - Load your windows but don't show it yet base.OnStartup(e); MainWindow main = new MainWindow(); // Step 4 - Make sure that the splash screen lasts at least two seconds timer.Stop(); int remainingTimeToShowSplash = MINIMUM_SPLASH_TIME - (int)timer.ElapsedMilliseconds; if (remainingTimeToShowSplash > 0) Thread.Sleep(remainingTimeToShowSplash); // Step 5 - show the page splash.Close(TimeSpan.FromMilliseconds(SPLASH_FADE_TIME)); main.Show(); } } }
You now have a splash screen with a nice, consistent user experience whether you have fast or slow hardware.
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.