I am trying to change the language of the DocumentViewer from default English to German but with no success.
Being new to WPF, I really struggle to do this.
IMPORTANT: DocumentViewer is created in code behind, in response to the menu item click, and then it is added as main window's Content.
I have tried doing the following, but it seems to do nothing:
myDocumentViewer.Language = System.Windows.Markup.XmlLanguage.GetLanguage("de-DE");
No changes are made, DocumentViewer keeps English.
Googling for proper usage of the Language property, I found nothing useful.
QUESTION:
How can I set the language of the DocumentViewer (created with code) to German?
What you are trying to accomplish can be done, but not very easily.
I'll start by pointing out that your test machine needs to have the appropriate language resources installed to permit DocumentViewer to show you tooltips etc. in German. In practice, this means that you'll need to have German (Germany) language pack installed on your computer. See Language Packs for details.
What comes below is what I know to the best of my understanding:
WPF does not quite have an in-built infrastructure, as far as I can tell, to dynamically adapt to changes in either Thread.CurrentThread.CurrentUILanguage or to changes in xml:lang(which is equivalent to FrameworkElement.Language property.
WPF controls primarily utilize xml:lang to determine their UI language (assuming that the corresponding UI resources are available), and it is up to the application developer to hook that up with Thread.CurrentThread.CurrentUILanguage if so desired. This in itself is not very hard to do using data-binding, like this:
<DocumentViewer Language="{Binding UILanguage, ConverterCulture={x:Static glob:CultureInfo.InvariantCulture}}" />
That still does not mean that the control thus data-bound would adapt its UI language to changes in Thread.CurrentThread.CurrentUILanguage. Every time you need the UI language to be changed, you need to recreate the control, remove the old control from the visual tree, and add the new one. Roughly, the code would look somewhat like this:
private void ChangeCulture()
{
string ietfLanguageTag = "de-DE";
var cultureInfo = CultureInfo.GetCultureInfo(ietfLanguageTag);
Thread.CurrentThread.CurrentCulture = cultureInfo;
Thread.CurrentThread.CurrentUICulture = cultureInfo;
UILanguage = ietfLanguageTag;
var parent = VisualTreeHelper.GetParent(_documentViewer) as Grid;
int index = parent.Children.IndexOf(_documentViewer);
parent.Children.Remove(_documentViewer);
_documentViewer = new DocumentViewer();
parent.Children.Add(_documentViewer);
}
The above snippet assumes that the visual parent of the DocumentViewer is a Grid, and it is backed by the variable _documentViewer.
Generally, the above solution is too simplistic and is not well suited for MVVM scenarios (which is often the case in WPF applications). You might have data bindings to the DocumentViewer instance, and creating new instances would require that those bindings be recreated in code (if, on the other hand, there happen to be no data-bindings involved, and all settings are set in code, then the above approach would just work, I think).
You can further improve this by creating a simple user control that encapsulates a DocumentViewer, along with any interesting bindings you might wish to preserve. Your control would look like this:
XAML:
<UserControl x:Class="LocalizedDocumentViewer.CultureAwareDocumentViewer"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:glob="clr-namespace:System.Globalization;assembly=mscorlib"
xmlns:local="clr-namespace:LocalizedDocumentViewer"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="300"
d:DesignWidth="300"
mc:Ignorable="d">
<Grid>
<DocumentViewer DataContext="{Binding}" Language="{Binding UILanguage, ConverterCulture={x:Static glob:CultureInfo.InvariantCulture}}" />
</Grid>
XAML.cs
using System.Windows.Controls;
namespace LocalizedDocumentViewer
{
public partial class CultureAwareDocumentViewer : UserControl
{
public CultureAwareDocumentViewer()
{
InitializeComponent();
}
}
}
Now, you can easily include this in your main application UI, like shown below. The XAML below includes a couple of additional UI elements (buttons and labels) that would help show a complete example:
MainWindow XAML:
<Window x:Class="DocViewerLoc.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:DocViewerLoc"
xmlns:localizedDocumentViewer="clr-namespace:LocalizedDocumentViewer;assembly=LocalizedDocumentViewer"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="DocumentViewer Culture Change Demo"
Width="525"
Height="350"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
mc:Ignorable="d">
<Grid>
<!-- Row and Column Definitions -->
<!-- Define a small row on the top of the window to place buttons -->
<Grid.RowDefinitions>
<RowDefinition Height="25" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<!-- Controls -->
<Button Grid.Row="0"
Grid.Column="0"
Command="{Binding CultureChanger}"
CommandParameter="{Binding RelativeSource={RelativeSource Self},
Path=Content}">
en-us
</Button>
<Button Grid.Row="0"
Grid.Column="1"
Command="{Binding CultureChanger}"
CommandParameter="{Binding RelativeSource={RelativeSource Self},
Path=Content}">
de-DE
</Button>
<Label Grid.Row="0" Grid.Column="2"><-- Click on one of these buttons to change UI culture</Label>
<Grid Grid.Row="1" Grid.ColumnSpan="3">
<localizedDocumentViewer:CultureAwareDocumentViewer x:Name="_documentViewer" DataContext="{Binding}" />
</Grid>
</Grid>
The corresponding code-behind has a couple of dependency properties used to help communicate with the bindings in the above XAML.
MainWindow.xaml.cs
using System;
using System.Globalization;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
namespace DocViewerLoc
{
public partial class MainWindow : Window
{
public MainWindow()
{
CultureChanger = new SimpleCommand(ChangeCulture);
InitializeComponent();
}
/// <summary>
/// ChangeCulture is called when one of the buttons with caption
/// 'en-us' or 'de-DE' is pressed.
/// </summary>
/// <param name="parameter">
/// A string containing the caption 'en-us' or 'de-DE'.
/// </param>
private void ChangeCulture(object parameter)
{
string ietfLanguageTag = parameter as string;
if (ietfLanguageTag == null) return;
var cultureInfo = CultureInfo.GetCultureInfo(ietfLanguageTag);
Thread.CurrentThread.CurrentCulture = cultureInfo;
Thread.CurrentThread.CurrentUICulture = cultureInfo;
// This will ensure that CultureAwareDocumentViewer's Language property
// binds to the updated value set here when it is instantiated next.
UILanguage = ietfLanguageTag;
// Remove the old instance of _documentViewer from the UI.
var parent = VisualTreeHelper.GetParent(_documentViewer) as Grid;
int index = parent.Children.IndexOf(_documentViewer);
parent.Children.Remove(_documentViewer);
// Create a new instance of CultureAwareDocumentViewer. This will
// use the updated value of UILanguage bind it to its Language (xml:lang)
// property, thus resulting in the appropriate language resources being
// loaded.
_documentViewer = new LocalizedDocumentViewer.CultureAwareDocumentViewer();
// Now, add the _documentViewer instance back to the UI tree.
parent.Children.Add(_documentViewer);
}
/// <summary>
/// ICommand used to bind to en-us and de-DE buttons in the UI
/// </summary>
#region CultureChange
public SimpleCommand CultureChanger
{
get { return (SimpleCommand)GetValue(CultureChangerProperty); }
set { SetValue(CultureChangerProperty, value); }
}
// Using a DependencyProperty as the backing store for CultureChanger. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CultureChangerProperty =
DependencyProperty.Register("CultureChanger", typeof(SimpleCommand), typeof(MainWindow), new PropertyMetadata(default(SimpleCommand)));
#endregion
/// <summary>
/// UILanguage property used to bind to the FrameworkElement.Language (xml:lang) property
/// in the DocumentViewer object within the CultureAwareDocumentViewer control.
/// </summary>
#region UILanguage
public string UILanguage
{
get { return (string)GetValue(UILanguageProperty); }
set { SetValue(UILanguageProperty, value); }
}
// Using a DependencyProperty as the backing store for UILanguage. This enables animation, styling, binding, etc...
public static readonly DependencyProperty UILanguageProperty =
DependencyProperty.Register("UILanguage", typeof(string), typeof(MainWindow), new PropertyMetadata(Thread.CurrentThread.CurrentUICulture.IetfLanguageTag));
#endregion
}
/// <summary>
/// Simple implementation of the ICommand interface that delegates
/// Execute() to an Action<object>.
/// </summary>
public class SimpleCommand : ICommand
{
#pragma warning disable 67
public event EventHandler CanExecuteChanged;
#pragma warning restore 67
public SimpleCommand(Action<object> handler)
{
_handler = handler;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_handler?.Invoke(parameter);
}
private Action<object> _handler;
}
}
The below screenshot show the resulting application UI. Note that the resources in DocumentViewer would switch between English and German, but the rest of the UI would not (because we did not try to localize our application!).
Application showing en-us resources in DocumentViewer:
Application showing de-DE resources in DocumentViewer:
AFAIK you are setting it correctly.
I do not have experience with DocumentViewer, but setting CurrentUICulture does not translate. Setting CurrentUICulture selects between resources that you have in your application for different languages. See https://stackoverflow.com/a/1142840/5569663 for an example. I assume that Language of a DocumentViewer is the same.
Unluckily setting the Language property of your DocumentViewer won't work. The reason of this issue is related to the PresentationUI assembly (it is a standard one), which contains resources which affect DocumentViewer.
The main point is that tooltips and labels are hardcoded in these resources and they are only in english (at least considering .NET 4).
For example, this is how the DocumentViewer's print button is defined (in the themes/generic.baml resource):
<Button Name="PrintButton" x:Uid="Button_14" ToolTip="Print (Ctrl+P)" ToolTipService.ShowOnDisabled="True" Width="24px" Padding="2,2,2,2" Margin="2,2,2,2" VerticalAlignment="Center" Command="ApplicationCommands.Print" IsTabStop="True" TabIndex="0" Style="{DynamicResource {ComponentResourceKey TypeInTargetAssembly={x:Type ui:PresentationUIStyleResources}, ResourceId=PUIDocumentViewerButtonStyle}}" Background="{DynamicResource {ComponentResourceKey TypeInTargetAssembly={x:Type ui:PresentationUIStyleResources}, ResourceId=PUIDocumentViewerPrintButton}}" CommandTarget="{Binding Path=TemplatedParent, RelativeSource={RelativeSource TemplatedParent}}" />
As you can see is defined as "Print (Ctrl+P)". You will find the same situation for other labels which you would localize. Even if x:Uid properties are defined LocBaml won't work since PresentationUI is not a satellite assembly.
So a first solution could be: write your own DocumentViewer style and you can use the language that you prefer. The problem is that inside a DocumentViewer there is a control named FindToolBar. It is declared as internal, so probably it would be hard to redefine its style.
Then I propose an alternative solution to you. My idea is based on the fact that localizable children of a DocumentViewer have a name (you can use ILSpy to establish it).
So you need just to extend DocumentViewer in this way:
public class LocalizedDocumentViewer : DocumentViewer
{
public LocalizedDocumentViewer()
{
Loaded += new RoutedEventHandler(OnLoaded);
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
Button button = FindChild<Button>(this, "PrintButton");
button.ToolTip = Properties.Resources.PrintToolTip;
button = FindChild<Button>(this, "CopyButton");
button.ToolTip = Properties.Resources.CopyToolTip;
button = FindChild<Button>(this, "FindPreviousButton");
button.ToolTip = Properties.Resources.FindPreviousToolTip;
button = FindChild<Button>(this, "FindNextButton");
button.ToolTip = Properties.Resources.FindNextToolTip;
Label label = FindChild<Label>(this, "FindTextLabel");
label.Content = Properties.Resources.FindTextLabel;
/* and so on... */
}
public static T FindChild<T>(DependencyObject parent, string childName)
where T : DependencyObject
{
/* see the link for the code */
}
}
You can find the code of FindChild method here (take a look to CrimsonX's answer).
I know, it is a unelegant solution. I do not like it too. But I guess it is fast and it allows you to preserve the default style look.
Related
My application has a lot of windows and most of them share some basic features. Because of that I extended the Window class to create a base for all my windows.
Everything compiles and displays fine but the designer just shows an empty window when I use my window class.
I made a basic example that can be easily used, my real window is much more complex but this shows the problem.
Here is the code:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Markup;
namespace WpfApplication1
{
[ContentProperty("ContentElement")]
public class MyWindow : Window
{
public ToolBar ToolBar { get; private set; }
public StatusBar StatusBar { get; private set; }
public Border ContentBorder { get; private set; }
public UIElement ContentElement
{
get { return (UIElement)GetValue(ContentElementProperty); }
set { SetValue(ContentElementProperty, value); }
}
public static readonly DependencyProperty ContentElementProperty = DependencyProperty.Register(
"ContentElement", typeof(UIElement), typeof(MyWindow),
new PropertyMetadata(null, (d, e) =>
{
MyWindow w = (MyWindow)d;
w.ContentBorder.Child = (UIElement)e.NewValue;
}));
public MyWindow() : base()
{
ToolBar = new ToolBar();
ToolBar.Height = 30;
ToolBar.VerticalAlignment = VerticalAlignment.Top;
StatusBar = new StatusBar();
StatusBar.Height = 20;
StatusBar.VerticalAlignment = VerticalAlignment.Bottom;
ContentBorder = new Border();
ContentBorder.SetValue(MarginProperty, new Thickness(0, 30, 0, 20));
Grid grid = new Grid();
grid.Children.Add(ToolBar);
grid.Children.Add(ContentBorder);
grid.Children.Add(StatusBar);
Content = grid;
}
}
}
XAML example for using MyWindow:
<local:MyWindow x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="300" Width="300">
<Grid>
<Rectangle Fill="Blue" />
</Grid>
</local:MyWindow>
Doing the exact same thing with a UserControl works just fine, also in the designer. Just replace every occurance of MyWindow with MyUserControl and extend from UserControl if you want to try that.
Is there any way I can get a custom Window like that to work with the designer, or do i have to make a UserControl and use that in every window?
Also, is this some kind of bug or intended behavior?
Addional info: I'm running Visual Studio 2015 Community and I'm using .net 4.6
I Also tried another approach. Instead of using the ContentPropertyAttribute i have overwritten the ContentProperty like this:
new public object Content {
get { return GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
new public static DependencyProperty ContentProperty = DependencyProperty.Register("Content", typeof(object), typeof(BaseUserControl), new PropertyMetadata(null, (s, e) =>
{
MyWindow bw = (MyWindow)s;
bw.ContentBorder.Child = (UIElement)e.NewValue;
}));
Again this works completely fine with a UserControl. With a Window I can at least see the Content in the designer now, but the ToolBar and StatusBar are still not showing up in the designer. When running it everything works correctly.
First, I am no super expert on WPF, but have done a bunch and think I can offer and help clarify some components. First, you can NOT derive from a .XAML based declaration of a WPF-Window, it can only be if entirely within code. I have come to find that sometimes the visual element building is much easier to do in XAML than it is within code, but both can and do work.
So, that said, I would like to offer a solution that might work for you. Starting with WPF Window Style / Templatea, if you are not already familiar with them, along with other controls you can run through their defaults.
First, I am starting with a RESOURCE DICTIONARY STYLE definition that will mimic much of what you may want in your default form. This becomes the stuff within the "ControlTemplate" of the style definition. I have created this as a file "MyWindowStyle.xaml" at the root level WpfApplication1 I created on my machine (just to match your sample project file namespace reference).
Inside the template, you could have almost anything... grids, dock panel, stack panels, etc. In this case, I have used a DockPanel and added your sample ToolBar, StatusBar and two extra labels just for sample. I also preset size and bogus color just to give visualization of the parts when you confirm their impact.
The CRITICAL element to look at is the . This identifies where the content for each of your DERIVED Windows content will be placed... Think of it as a place-holder for each one of your forms for individuality while the rest of the form, its controls all remain consistent. You will see it come into play as you play around with it.
The content of it is and notice the style x:Key="MyWindowStyle". This coincidentally is the same as the xaml, but you could have 100's of styles within a single resource dictionary. I am keeping simple to just the one for demo purposes.
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
<Style x:Key="MyWindowStyle" TargetType="Window">
<Setter Property="SnapsToDevicePixels" Value="true" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Window}">
<Grid>
<Grid.Background>
<SolidColorBrush Color="{DynamicResource WindowColor}"/>
</Grid.Background>
<AdornerDecorator>
<DockPanel LastChildFill="True" Background="Blue">
<!-- List items docked to the top based on top-most first going down -->
<ToolBar x:Name="tmpToolBar" Height="45" DockPanel.Dock="Top" />
<Label Content="Testing by Style"
Height="30" Width="150" DockPanel.Dock="Top"/>
<!-- When docking to the bottom, start with bottom most working up -->
<StatusBar x:Name="tmpStatusBar" Height="30"
Background="Yellow" DockPanel.Dock="Bottom" />
<Label Content="Footer area based from style"
Height="30" Width="250" DockPanel.Dock="Bottom" />
<!-- This one, since docked last is rest of the space of the window -->
<ContentPresenter DockPanel.Dock="Bottom"/>
</DockPanel>
</AdornerDecorator>
<ResizeGrip x:Name="WindowResizeGrip"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Visibility="Collapsed"
IsTabStop="false" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="ResizeMode" Value="CanResizeWithGrip">
<Setter TargetName="WindowResizeGrip"
Property="Visibility" Value="Visible" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Next, we need to make this publicly available for the entire duration of the application, including availability within the designer mode... Within your projects "App.xaml" which is the startup for the application, it will have a default and empty area. Replace it with this.
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/WpfApplication1;component/MyWindowStyle.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
Now, to a CODE-ONLY (not a .xaml window based definition) of your "MyWindow.cs" class. If you look at the style where I declared the toolbar and statusbar, I assigned them the names of "tmpToolBar" and "tmpStatusBar" respectively. Notice the [TemplatePart()] declarations. I am now expecting the template to HAVE these controls by the given name within the TEMPLATE somewhere.
Within the constructor, I am loading the Style from the App.xaml resource dictionary being fully available. Then I follow-up with the OnApplyTemplate() which I typically heavily document my code so anyone following me has some idea how / where things originated from and self explanatory.
My entire "MyClass.cs" is below
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
namespace WpfApplication1
{
[TemplatePart(Name = "tmpToolBar", Type = typeof(ToolBar))]
[TemplatePart(Name = "tmpStatusBar", Type = typeof(StatusBar))]
public class MyWindow : Window
{
protected ToolBar myToolBar;
protected StatusBar myStatusBar;
public MyWindow() : base()
{
// NOW, look for the resource of "MyWindowStyle" within the dictionary
var tryStyle = FindResource("MyWindowStyle") as Style;
// if a valid find and it IS of type Style, set the style of
// the form to this pre-defined format and all it's content
if (tryStyle is Style)
Style = tryStyle;
}
// the actual template is not applied until some time after initialization.
// at that point, we can then look to grab object references to the controls
// you have need to "hook up" to.
public override void OnApplyTemplate()
{
// first allow default to happen
base.OnApplyTemplate();
// while we get the style loaded, we can now look at the expected template "parts"
// as declared at the top of this class. Specifically looking for the TEMPLATE
// declaration by the name "tmpToolBar" and "tmpStatusBar" respectively.
// get object pointer to the template as defined in the style template
// Now, store those object references into YOUR Window object reference of Toolbar
var myToolBar = Template.FindName("tmpToolBar", this) as ToolBar;
if (myToolBar != null)
// if you wanted to add your own hooks to the toolbar control
// that is declared in the template
myToolBar.PreviewMouseDoubleClick += myToolBar_PreviewMouseDoubleClick;
// get object pointer to the template as defined in the style template
var myStatusBar = Template.FindName("tmpStatusBar", this) as StatusBar;
if (myStatusBar != null)
myStatusBar.MouseDoubleClick += myStatusBar_MouseDoubleClick;
// Now, you can do whatever else you need with these controls downstream to the
// rest of your derived window controls
}
void myToolBar_PreviewMouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
// in case you wanted to do something based on PreviewMouseDoubleClick of the toolbar
MessageBox.Show("ToolBar: Current Window Class: " + this.ToString());
}
void myStatusBar_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
// in case something for MouseDoubleClick on the StatusBar
MessageBox.Show("StatusBar: Current Window Class: " + this.ToString());
}
}
}
So now, lets put it into place. Have your application's main window derive from the MyWindow class. The only thing you need there is
namespace WpfApplication1
{
public partial class MainWindow : MyWindow
{}
}
In the DESIGNER of your form, put in a few controls, such as label, textbox, whatever. You do not see your actual other style yet, but just go with it. Save and run the sample app. Your main window should be displayed with the entire pre-defined template there ALONG WITH the few extra control you had placed specifically on this form.
Now, to get the full visualization in your "MainWindow" from the designer perspective. Within the .xaml area of
<my:MyWindow
x:Class="WpfApplication1.MainWindow"
[other declarations] >
just add the following before the close ">"
Style="{StaticResource MyWindowStyle}"
The resource is available via the App.xaml at startup and you should now be able to see the entire visual while you are designing... but you cant change the outermost template, just the content specific to this one page as mentioned about the "ContentPresenter" part of the template definition. What you are changing is within the place-holder portion allocated by the Template. If you want to change the main part of the window controls, you need to update the TEMPLATE!
But here is part of the trick of the template designer. Start with this, and build in what you need visually, get it placed right and once ready, take it out of here and put into the template and now it is applicable to the rest of all windows. Fix fonts, sizes, colors, whatever.
Hope this helps, and if you have any questions for follow-up, let me know.
Window class is very complex in compare to UserControl class. Microsoft has written more than 8k lines of code in Window class compare to 80 lines in UserControl, additionally Window class contain many operation/event/restriction on content property, and any one piece of code is hindering in rendering the content when you use [ContentProperty("ContentElement")] with the Window subclass MyWindow .
Probably making it a UserControl is better option, If not possible you can write some code temporarily(copy code from ContentElement property) in content property to see the design view.
<lib:MyWindow.Content>
<Button Content="Click" Width="200" />
</lib:MyWindow.Content>
and then just remove the code before run time. (Not a good idea but, whatever works.:) and I suspect that you have already figured that out.
I have a User Control A which contains within it another User Control B.
User Control A has a dependency property DeviceColour. This determines the Colour of the internal User Control B.
The registration of this Dependency property is as follows:
public static readonly DependencyProperty DeviceColourProperty = DependencyProperty.Register("DeviceColour",
typeof(vhBase.DeviceColours),typeof(ucBaseDevice),new FrameworkPropertyMetadata(vhBase.DeviceColours.Gray,
FrameworkPropertyMetadataOptions.AffectsRender,new PropertyChangedCallback(DeviceColourProperty_PropertyChanged)));
public vhBase.DeviceColours DeviceColour
{
get { return (vhBase.DeviceColours)GetValue(DeviceColourProperty); }
set { SetValue(DeviceColourProperty, value); }
}
private static void DeviceColourProperty_PropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
ucBaseDevice device = (ucBaseDevice)obj;
device.VisualDevice.DeviceColour = (vhBase.DeviceColours)e.NewValue;
}
In the above property change event, VisualDevice is a property that points to the Internal User Control B as shown below:
public override vhBase VisualDevice
{
get { return vhMain; }
}
vhMain is User Control B which I can see and manipulate in the XAML designer.
The problem I'm having at design time, is that VisualDevice appears to be returning NULL i.e. vhMain, the user control is null and therefore "object reference not set to an instance....".
This works at runtime, but I need it to also work at design time.
As you've perhaps realised, I don't know too much about DP and, well, WPF in general and I'm finding myself going around in circles.
Can anyone please help?
Thanks
I've make a simple sample of what-i-think-is-right way to bind child's property to parent's one.
I used the Color property as an example, but that's the same for every propery type.
There are two user controls: ControlB with DP 'Colour' and ControlA with DP DeviceColour. ControlB just painting its background to the specified color:
<UserControl x:Class="WpfApplication1.ControlB" <!--skipped--> Name="controlB">
<Grid>
<Grid.Background>
<SolidColorBrush Color="{Binding ElementName=controlB, Path=Colour}" />
</Grid.Background>
</Grid>
</UserControl>
And there is how to bind that properties from XAML in ControlA:
<UserControl x:Class="WpfApplication1.ControlA" <!--skipped--> Name="controlA">
<Grid>
<my:ControlB Colour="{Binding ElementName=controlA, Path=DeviceColour}" />
</Grid>
</UserControl>
And there is how to use ControlA:
<Window x:Class="WpfApplication1.MainWindow" <!--skipped-->>
<Grid>
<my:ControlA DeviceColour="Green" />
</Grid>
</Window>
I am creating a custom control that when invoked in the XAML can be set to only allow certain types of inputs:
<lib:CustomControl RestrictTo="UnsignedIntegersOnly" ... ></CustomControl>
Where the UnsignedIntegersOnly is part of an Enum containing the set of allowed restrictions.
If the user inputs something that is not allowed, the control will throw a validation error and not allow him to continue to the next form/page/etc.
My vision for implementing this, was to, in the underlying TextBox that makes up this control, bind its text field to a validation rule which will be passed as an input the RestrictTo value that was specified in the CustomControl XAML declaration. Then in that ValidationRule class, handle the RestrictTo specific validation and return whether the validation was successful or not.
This is where I am not quite sure how to proceed. Is it even possible to pass arguments to the ValidationRule in such a seemingly dynamic manner? I am setting a property, RestrictTo, of my control and then passing that to its validation.
If it is possible, how would it be done? What sort of binding or resource linking should I use?
You might be interested in using a MaskedTextBox control, it will restrict what the user can input in the TextBox.
As there's no official control from Microsoft for WPF I would suggest the following from Xceed :
MaskedTextBox (it's free to use :-)
Here you have the syntax of the mask :
http://msdn.microsoft.com/en-us/library/system.windows.forms.maskedtextbox.mask.aspx
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
Title="MainWindow" Height="350" Width="525">
<Grid>
<xctk:MaskedTextBox Mask="0000"></xctk:MaskedTextBox>
</Grid>
</Window>
If you have Visual Studio 2012 you can easily get this package through NuGet :
(right-click on your project )
Note : on my answer to your previous question, I extensively used validation rules in the link I posted but I'd say that if there are some times where you can avoid it through the means of a well-crafted component/control, then it's wise to do so. As #Barn pointed out on your previous question, a generic validation rule might be a hard thing to do and somewhat questionable as you'll have to handle all the types in it, IMO it's a little counter-intuitive as validators and converters are generally specific against being generalist; you're likely to waste more time on it than it's worth and it will be probably less re-usable than you think it could be. (source : my experience)
Below code should get you started. It is a user control and not a custom control. I recomend you get your code working as a user control first and then convert it to a custom control. Bind Valid property to some property in your viewmodel that controls user workflow.
XAML:
<UserControl x:Class="WpfApplication.ValidatingControl"
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"
mc:Ignorable="d" >
<StackPanel Orientation="Horizontal">
<TextBox Name="_textBox" TextChanged="OnTextChanged" Background="LightGray" Width="200"/>
<TextBlock Name="_messageText" Foreground="Red" />
</StackPanel>
</UserControl>
Code behind:
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication
{
public partial class ValidatingControl : UserControl
{
public ValidatingControl()
{
InitializeComponent();
}
public enum Restrictions
{
UnsignedIntegersOnly,
SmallIntegersOnly
}
public static readonly DependencyProperty RestrictToProperty =
DependencyProperty.Register("RestrictTo", typeof(Restrictions), typeof(ValidatingControl), new PropertyMetadata(Restrictions.UnsignedIntegersOnly));
public Restrictions RestrictTo
{
get { return (Restrictions)GetValue(RestrictToProperty); }
set { SetValue(RestrictToProperty, value); }
}
public bool Valid
{
get { return (bool)GetValue(ValidProperty); }
set { SetValue(ValidProperty, value); }
}
public static readonly DependencyProperty ValidProperty =
DependencyProperty.Register("Valid", typeof(bool), typeof(ValidatingControl), new UIPropertyMetadata(false));
private void OnTextChanged(object sender, TextChangedEventArgs e)
{
ValidateText(RestrictTo, _textBox.Text);
}
private void ValidateText(Restrictions restrictTo, string text)
{
// validate text, update _messageText, update Valid
}
}
}
The Issue: I have a newly made UserControl with a couple telerik controls in a parent Grid to be able to use it throughout the solution. Sounds simple enough right? I created the UserControl, let's call the Class My.Project.Controls.Tool, which I then tried to call to another View with the namespace xmlns:Controls="clr-namespace:My.Project.Controls;assembly=My.Project.GlobalUserControlDump" and then set it in the view via easily selected from the handy dandy intellisense.
This does as is expected, my UserControl appears on the separate view in the designer just fine. So I take the next normal step and build it....as soon as the build completes (which it does just fine with no errors reported as expected) the little bugger disappears! The xaml is still on the View of course, but it's disappeared from the designer AND it doesnt appear on the built solution?
Confused I go back, make a quick change to the UserControl and it appears back in the designer. Ok, I think it must be some fluke so I build it again....and again it disappears from the designer AND the built solution?
Now I can continue to reliably reproduce this scenario. Make a little change to the UserControl, it re-appears in the designer.....then build it and it disappears again!
Could someone pretty please shed some light on this quandry? Running in SL (both in and out of browser but built in browser) with Caliburn Micro. Any insight to this mystery is of course greatly appreciated, hoping another pair of eyes can catch my folly. Cheers!
For Clarification, this is what sits in the user control that directly related to a previous question.
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
mc:Ignorable="d"
x:Class="My.Project.Controls.DatePicker">
<Grid Width="90">
<telerik:RadDateTimePicker
InputMode="DatePicker"
SelectedDate="{Binding SelectedDate, Mode=TwoWay}"/>
<telerik:RadMaskedDateTimeInput
IsClearButtonVisible="False"
FormatString="{}{0:M/d/yy}"
SelectionOnFocus="SelectAll"
Value="{Binding SelectedDate, Mode=TwoWay}"/>
</Grid>
</UserControl>
Which I would then invoke directly on a view like (It would sit in the GlobalUserControlDump project as the namespace shows) and once the namespace is added to the View, it shows up fine in the intellisense as expected;
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:cal="clr-namespace:Caliburn.Micro;assembly=Caliburn.Micro"
xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
x:Class="My.Project.Views.RandomView"
xmlns:Controls="clr-namespace:My.Project.Controls;assembly=My.Project.GlobalUserControlDump"
mc:Ignorable="d">
<Grid>
<Controls:DatePicker />
</Grid>
</UserControl>
Then I'm exposing the property I need via;
namespace My
{
public partial class DatePicker : UserControl
{
public static readonly DependencyProperty SelectedDateProperty =
DependencyProperty.Register("SelectedDate", typeof(DateTime), typeof(DatePicker), new PropertyMetadata(null));
public DatePicker()
{
// Required to initialize variables
DataContext = this;
}
public DateTime SelectedDate
{
get { return (DateTime)GetValue(SelectedDateProperty); }
set { SetValue(SelectedDateProperty, value); }
}
}
}
Thanks for taking a look, has me still currently stumped.
You are missing a call to InitializeComponent() in you UserControl's constructor.
I believe your namespace in the code you posted is incorrect.
By having new PropertyMetadata(null)); a property changed callback isn't being registered. Without that I believe binding won't work. What you want to do is bind to the property on your usercontrol and when the bound value changes, you want to set the value on the RadDateTimePicker contained in your control.
xaml:
<Grid Width="90">
<telerik:RadDateTimePicker x:Name="MyRadDateTimePicker" InputMode="DatePicker" />
<telerik:RadMaskedDateTimeInput x:Name="MyRadMaskedDateTimeInput"
IsClearButtonVisible="False"
FormatString="{}{0:M/d/yy}"
SelectionOnFocus="SelectAll" />
</Grid>
</UserControl>
code behind:
using System;
using System.Windows;
namespace SO
{
public partial class MyDatePicker
{
public MyDatePicker()
{
InitializeComponent();
}
public const string SelectedDatePropertyName = "SelectedDate";
public DateTime SelectedDate
{
get { return (DateTime)GetValue(SelectedDateProperty); }
set { SetValue(SelectedDateProperty, value); }
}
public static readonly DependencyProperty SelectedDateProperty = DependencyProperty.Register(
SelectedDatePropertyName,
typeof(DateTime),
typeof(MyDatePicker),
new PropertyMetadata(DateTime.Now, OnSelectedDatePropertyChanged));
private static void OnSelectedDatePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((MyDatePicker)d).MyRadDateTimePicker.SelectedDate = (DateTime) e.NewValue;
((MyDatePicker)d).MyRadMaskedDateTimeInput.Value = (DateTime)e.NewValue;
}
}
}
I want to use a dependency property so that my label displays values selected in the Listbox. This is just to more clearly understand the working of a dependency property.
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WPFToolkit="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit"
xmlns:local="clr-namespace:WpfApplication1"
x:Name="MyWindow"
Height="200"
Width="300">
<StackPanel>
<ListBox x:Name="lbColor"
Width="248"
Height="56"
ItemsSource="{Binding TestColor}"/>
<StackPanel>
<Label Content="{Binding Path=Test, ElementName=lbColor}" />
</StackPanel>
</StackPanel>
</Window>
Code Behind,
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public ObservableCollection<string> TestColor { get; set; }
public String Test
{
get { return (String)GetValue(TestProperty); }
set { SetValue(TestProperty, value); }
}
// Using a DependencyProperty as the backing store for Title. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TestProperty =
DependencyProperty.Register("Test", typeof(String), typeof(ListBox), new UIPropertyMetadata("Test1"));
public Window1()
{
InitializeComponent();
TestColor = new ObservableCollection<string>();
DataContext = this;
TestColor.Add("Red");
TestColor.Add("Orange");
TestColor.Add("Yellow");
TestColor.Add("Green");
TestColor.Add("Blue");
}
}
}
Can anyone explain to me how will I accomplish this using a dependency property? Somehow I am very confused with the Dependency Property concept, and I just wanted to see a working example for that.
You'll need to have your ListBox "select" the current text:
<StackPanel>
<!-- Add selected item binding -->
<ListBox
x:Name="lbColor" Width="248" Height="56"
ItemsSource="{Binding TestColor}"
SelectedItem="{Binding Test}"
/>
<StackPanel>
<!-- No need for elementname - just use Test on the DataContext -->
<Label Content="{Binding Path=Test}" />
</StackPanel>
</StackPanel>
I like to think of Data Binding as Big Brother. The Binding system sets itself up to watch all of its various registered Bindings and when the proper criteria have occurred (for example, FocusLost or PropertyChanged), the Binding system copies the source value to the target. For a TwoWay or OneWayToSource binding, the Binding system will even copy from the target to the source, if the right criteria happen.
The target has to be a DependencyProperty, as this is a special kind of property that knows how to Depend on other values. What the XAML {Binding} is doing under the covers is creating a new BindingExpression and then calling BindingOperations.SetBinding, which registers a particular BindingExpression with the Binding System, so it knows how to watch and perform the updates.
The advantage of this is that neither the target nor the source needs to take the responsibility for writing code to explicitly update the other. If I have code that knows how to provide a list of Colors, why should I care how the Colors get represented to the user? Because the Binding System takes care of the binding, it doesn't matter to me if the target using my Colors is a listbox, a treeview, or any other object that knows how to handle a list of items. I can focus on what I care about (the public interface I'm exposing) and don't have to care about the messy details of gluing that interface to something else.