Making UserControls accessible in WPF - wpf

I have a WPF form, which consists of a grid of two columns.
In the left-hand column are the control labels, and in the right-hand column are my controls.
The controls are all UserControls. In the simplest case, some of these controls simply wrap existing WPF controls such as the textbox, so that they all implement a common interface.
When the form is generated, I have code like this to set the label for the associated control, where newControl is the created UserControl and ctl.Caption simply returns the required label text:
Label newLabel = new Label();
newLabel.Content = ctl.Caption + ":";
newLabel.Target = newControl;
One problem is that setting the Target doesn't actually work. If I have an underscore in the caption, the mnemonic key doesn't set focus to the wrapped control. One workaround for this may be to manually set the focus to the wrapped control within the UserControl code - but...
The biggest problem is accessibility. Screenreaders such as JAWS, and Windows built-in Narrator, do not read the control caption when the control receives focus.
I have had a look at this: http://msdn.microsoft.com/en-us/library/windows/desktop/gg712258.aspx - which provides a lot of detail, but no helpful examples. It has a lot of stuff about custom controls, which is surely overkill for a simple user control?
So, how can I "attach" my labels correctly to my UserControls?
You can browse the code for the entire project at http://quest.codeplex.com/SourceControl/changeset/view/676933506953 - the particular code is in the EditorControls project, and the UserControls are instantiated in ElementEditor.xaml.cs.

Your newControl is of type Control that doesn't allow you to add additional content.
If you want to add some content to it you need to use a class that supports it, like ContentControl or Panel (for multiple childs) you can implement you own control that implements the IAddChild interface.
A simple solution for you problem could be :
<UserControl x:Class="UIMocks.MyCustomControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel x:Name="content"/>
</UserControl>
The code-behind
[ContentProperty("Children")]
public partial class MyCustomControl : UserControl
{
public MyCustomControl()
{
InitializeComponent();
}
public UIElementCollection Children { get { return content.Children; } }
}
and then you can use
MyCustomControl newControl = InitialiseEditorControl(ctl);
...
Label newLabel = new Label();
newLabel.Content = ctl.Caption + ":";
newControl.Children.Add(newLabel);

hmmm I tried to reproduce your issue on a small test project, but for me it works... so I guess you'll have to give more details on how your userControls are built. Here is what works for me:
I created an Empty project (just the App and Window files, as usual) and set up a grid with 2 columns in my window:
<Window x:Class="Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Name="Window"
SizeToContent="WidthAndHeight">
<Grid Name="MyGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
</Grid>
</Window>
then created a userControl that extends the wpf TextBox class:
<TextBox x:Class="Test.MyTextBox"
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"
d:DesignHeight="300" d:DesignWidth="300">
</TextBox>
and:
using System.Windows;
using System.Windows.Controls;
namespace Test
{
public partial class MyTextBox : TextBox
{
public static readonly DependencyProperty CaptionProperty =
DependencyProperty.Register("Caption", typeof(string), typeof(MyTextBox), new UIPropertyMetadata(""));
public string Caption
{
get { return (string)GetValue(CaptionProperty); }
set { SetValue(CaptionProperty, value); }
}
public MyTextBox()
{
InitializeComponent();
}
}
}
it's basically a textbox with a "Caption" dp.
and now in My window's code behind:
public MainWindow()
{
InitializeComponent();
MyTextBox tb = new MyTextBox { Caption = "_Foo", Width = 100 };
Label lb = new Label { Content = tb.Caption + ":", Target = tb };
MyGrid.Children.Add(lb);
MyGrid.Children.Add(tb);
Grid.SetColumn(lb, 0);
Grid.SetColumn(tb, 1);
}
and with this, I do get focus on the TB when I press ALT + F (I can even see the _ under the F of "Foo" in the Label when just pressing ALT)
So I guess your issue has to do with your UserControls themselves and how they are built (what Template for instance)
Edit:
If your control is not extending an existing control but rather contains a WPF control, the issue is probably on the Focus method. You should add a Focus() method that sets the focus on the right part of your control when the control itself gets the focus.
code (for a UserControl containing a textbox that you want to get the focus for instance):
<TextBox x:Class="Test.MyTextBox"
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"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Button Content="foo" Grid.Column="0" />
<TextBox Name="TextBoxPart" Grid.Column="1" />
</Grid>
</TextBox>
code behind
public partial class MyTextBox : TextBox
{
public static readonly DependencyProperty CaptionProperty =
DependencyProperty.Register("Caption", typeof(string), typeof(MyTextBox), new UIPropertyMetadata(""));
public string Caption
{
get { return (string)GetValue(CaptionProperty); }
set { SetValue(CaptionProperty, value); }
}
public MyTextBox()
{
InitializeComponent();
}
protected override void OnGotFocus(RoutedEventArgs e)
{
TextBoxPart.Focus();
}
}
Edit 2:
I had an issue once, to transfer the focus to a subcontrol in a dataGridCell, and here is what I did in the template:
<ControlTemplate.Triggers>
<Trigger Property="IsFocused" Value="True">
<Setter TargetName="TextBoxPart" Property="FocusManager.FocusedElement" Value="{Binding ElementName=TextBoxPart}" />
</Trigger>
</ControlTemplate.Triggers>
you could try adding this to your template. This should transfer your focus alright.
as for the accessibility, I don't think this will help, but I do not see any way of achieving what you want :-/

Related

Simple custom User Control clone its values to various other elements on Frame.GoBack()

I was recently working on a user control, let's name it TestUserControl, and used two of its instances on one page.
While I was testing, I noticed, that when I type something into the values of upper TestUserControl, go to another page then go back to the first page - the second instance of TestUserControl is filled with values that had been typed into the first one (and even elements which are not part of user control of type TestUserControl are affected!).
Here are the screenshots of described behavior .
And the code of a simple project from which this screenshots come:
Landing page:
<Page x:Class="PageNavigation.Pages.Landing"
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:local="clr-namespace:PageNavigation.Pages"
xmlns:controls="clr-namespace:PageNavigation.Controls"
xmlns:n="clr-namespace:PageNavigation"
mc:Ignorable="d"
d:DesignHeight="200"
d:DesignWidth="800"
Title="Landing">
<Grid Background="White"
ButtonBase.Click="Grid_Click">
<WrapPanel Margin="5">
<TextBlock Margin="0 25"
Text="I am a simple text block" />
<n:NavButton Text="Accounts"
ImageSource="/Images/Accounts.png"
NavUri="/Pages/Accounts.xaml" />
<n:NavButton Text="Bills"
ImageSource="/Images/Billing.png"
NavUri="/Pages/Bills.xaml" />
<n:NavButton Text="Employees"
ImageSource="/Images/Employees.png"
NavUri="/Pages/Employees.xaml" />
<n:NavButton Text="Setting"
ImageSource="/Images/Settings.png"
NavUri="/Pages/Setting.xaml" />
<StackPanel Orientation="Horizontal">
<TextBlock Text="User controls:" />
<controls:TestUserControl Width="150"/>
<controls:TestUserControl Width="150"/>
</StackPanel>
</WrapPanel>
</Grid>
</Page>
using System.Windows;
using System.Windows.Controls;
namespace PageNavigation.Pages
{
public partial class Landing : Page
{
public Landing()
{
InitializeComponent();
}
private void Grid_Click(object sender, RoutedEventArgs e)
{
if (e.OriginalSource is not NavButton ClickedButton)
return;
NavigationService.Navigate(ClickedButton.NavUri);
}
}
}
My testing user control:
<UserControl x:Class="PageNavigation.Controls.TestUserControl"
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:local="clr-namespace:PageNavigation.Controls"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel Margin="5">
<TextBox/>
<TextBox/>
<TextBox/>
<StackPanel Margin="10">
<ComboBox>
<ComboBoxItem>Item 1</ComboBoxItem>
<ComboBoxItem>Item 2</ComboBoxItem>
<ComboBoxItem>Item 3</ComboBoxItem>
</ComboBox>
</StackPanel>
<ToggleButton>Toggle me!</ToggleButton>
</StackPanel>
</UserControl>
using System.Windows.Controls;
namespace PageNavigation.Controls
{
public partial class TestUserControl : UserControl
{
public TestUserControl()
{
InitializeComponent();
}
}
}
Navigation button to another pages (these one with images):
using System;
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
namespace PageNavigation
{
public class NavButton : ButtonBase
{
static NavButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(NavButton), new FrameworkPropertyMetadata(typeof(NavButton)));
}
public static readonly DependencyProperty ImageSourceProperty = DependencyProperty.Register("ImageSource", typeof(ImageSource), typeof(NavButton), new PropertyMetadata(null));
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(NavButton), new PropertyMetadata(null));
public static readonly DependencyProperty NavUriProperty = DependencyProperty.Register("NavUri", typeof(Uri), typeof(NavButton), new PropertyMetadata(null));
public ImageSource ImageSource
{
get { return (ImageSource)GetValue(ImageSourceProperty); }
set { SetValue(ImageSourceProperty, value); }
}
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public Uri NavUri
{
get { return (Uri)GetValue(NavUriProperty); }
set { SetValue(NavUriProperty, value); }
}
}
}
One of example pages which contains back button:
<Page x:Class="PageNavigation.Pages.Employees"
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:local="clr-namespace:PageNavigation.Pages"
mc:Ignorable="d"
d:DesignHeight="450"
d:DesignWidth="800"
Title="Employees">
<Grid Background="White">
<Button Content="Back"
Padding="3"
Command="NavigationCommands.BrowseBack"
BorderThickness="0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Margin="5,5,0,0" />
<Label Content="Employees"
FontSize="50"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Grid>
</Page>
using System.Windows.Controls;
namespace PageNavigation.Pages
{
public partial class Employees : Page
{
public Employees()
{
InitializeComponent();
}
}
}
Main window:
<Window x:Class="PageNavigation.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:PageNavigation"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Frame Source="/Pages/Landing.xaml" NavigationUIVisibility="Hidden"/>
</Grid>
</Window>
using System.Windows;
namespace PageNavigation
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}
I'm using for the navigation System.Windows.Navigation.NavigationService and Frame, which seems to be important, as I couldn't reproduce this bug in a simple application that uses switching ContentControl and custom NavigationService.
When I use binding to view model in TestUserControl values, the problem seems to disappear, but what if I do not need binding, because I use elements of control to manage its internal behavior only - using for example toggle button to hide/show content of a text box field?
I was looking for an explanation for this behavior, but could not find any. I was reading about defining user controls, journal in Navigation Service, Data Context, and keeping alive pages when using Frames, but found nothing about user controls magically cloning their values to other controls.
I understand I could miss something simple or I defined all my user controls wrongly, but... this is not a behavior I would expect from a user control created in the simplest possible way.
I would appreciate it if someone could help me and answer my questions:
why is this happening? Is it a bug in WPF itself?
is this possible to create "safe" user control without necessarily using binding its values to an external source - and how to do it properly?
Sorry if I did not provide all the required information, but I don't know which information will be valuable as it seems to be a vague problem. I will try to answer any questions.
It's the frame journal which stores state for pages which have been shown.
If you don't go back to the previous page in your real app you could do:
NavigationService.RemoveBackEntry()
After you navigate.
That should remove whatever the journal just got.
You could also try giving things explicit X:Name and see if that allows the journal mechanism to differentiate.
Personally, I avoid frames and pages and I suggest you might consider using contentpresenter and usercontrols instead. I prefer viewmodel first navigation.
The discovered workaround is to bind values of controls to properties in code behind (it solves the problem) - at least visually.

Binding does not work at first load

I have a button in a UserControl. The code I supply here is simple, just to illustrate you the situation. That's why I included a second button to allow significative user interaction. The UserControl xaml code is as follows:
<UserControl x:Class="WpfControlLibrary1.UserControl1"
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:local="clr-namespace:WpfControlLibrary1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300" x:Name="MyUserControl">
<Grid>
<Button Name="btSelectColor" ToolTip="Select color" Width="23" Height="23" BorderBrush="#FF070707" Background="{Binding Mode=OneWay, ElementName=MyUserControl, Path=CurrentColorBrush}"></Button>
<Button Name="btChangeColor" ToolTip="For change color for testing" Width="120" Height="23" Margin="90,166,90,110" Click="btChangeColor_Click">Test color changing</Button>
</Grid>
</UserControl>
The ElementName property value is MyUserControl, which is the same value of the UserControl x:Name attribute. The Path value is CurrentColorBrush which is the wrap of a dependency property defined in the code behind as follows:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace WpfControlLibrary1
{
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
CurrentColorBrush = new SolidColorBrush(Color.FromArgb(1, 150, 250, 150));
}
public static readonly DependencyProperty currentColorBrushProperty = DependencyProperty.Register("CurrentColorBrush", typeof(SolidColorBrush), typeof(UserControl1));
public SolidColorBrush CurrentColorBrush
{
get
{
return (SolidColorBrush)GetValue(currentColorBrushProperty);
}
set
{
SetValue(currentColorBrushProperty, value);
}
}
private void btChangeColor_Click(object sender, RoutedEventArgs e)
{
CurrentColorBrush = new SolidColorBrush(Color.FromArgb(1, System.Convert.ToByte(CurrentColorBrush.Color.R - 20), 15, 15));
}
}
}
I set a default value to this property in the constructor of the UserControl using the following statement:
CurrentColorBrush = new SolidColorBrush(Color.FromArgb(1, 150, 250, 150));
The problem is that, when the window containing the Usercontrol is shown, the button background is not of the default color defined in the UserControl’s constructor. Even if the second button is clicked, the background of the first button remains the same. Weirder, if the Visual Tree is inspected in runtime you can see that the expected values are in place but you never see the change graphically . The UserControl is in a separate WPF User Control Library project. I'm using Visual Studio 2017.
EDITED*****************************
The solution proposed by Aybe gave me a clue. The code works and is similar to mine. So, I started to look for differences and I realized that the initial value for CurrentBrush in my code is a SolidColorBrush defined from a Color. This Color was defined from RGBA values picked under no special criteria. The fact is that, when I use (as Aybe did) standard values of brushes like Brushes.DarkCyan everything works well. Maybe I was providing RGB values that create a color which is not valid for Background property. I don't know if there are any limitations of this kind but the behavior of my code could point to something like this. I googled it but I couldn't find anything about this matter. Do you have any idea?
Here is the smallest possible example that works, for every case you mentioned:
Window:
<Window
x:Class="WpfApp1.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:WpfApp1"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<StackPanel>
<local:UserControl1 x:Name="uc1" />
<Button
Background="{Binding ElementName=uc1, Path=CurrentBrush}"
Click="Button_Click"
Content="Button1" />
</StackPanel>
</Window>
Window:
using System.Windows;
using System.Windows.Media;
namespace WpfApp1
{
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
// this will work but beware that button focus hides it
// move the mouse away from button to see that it worked
uc1.CurrentBrush = Brushes.PaleVioletRed;
}
}
}
Control:
<UserControl
x:Class="WpfApp1.UserControl1"
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"
mc:Ignorable="d">
<Grid>
<TextBlock Text="Hello, world !" />
</Grid>
</UserControl>
Control:
using System.Windows;
using System.Windows.Media;
namespace WpfApp1
{
public partial class UserControl1
{
// the default value is specified in the metadata, below
// I changed it from default(Brush) to Aqua
public static readonly DependencyProperty CurrentBrushProperty = DependencyProperty.Register(
"CurrentBrush", typeof(Brush), typeof(UserControl1), new PropertyMetadata(Brushes.Aqua));
public UserControl1()
{
InitializeComponent();
// this will override the default value above
CurrentBrush = Brushes.DarkCyan;
}
public Brush CurrentBrush
{
get => (Brush) GetValue(CurrentBrushProperty);
set => SetValue(CurrentBrushProperty, value);
}
}
}
Note: beware of the XAML designer of Visual Studio, sometimes changes are not reflected, i.e. prefer the XAML designer of Expression Blend
Try to use a specific overload of your dependency property creation that allow you to specify a default value
here is an article about the dp's default value

How to show floating virtual keyboard (user control) in MainWindow when an input control (from another user control) has been set to focus in WPF?

I have been doing development work in WPF application which uses an MVVM pattern for a couple of days now. I'm very new to WPF and MVVM pattern as well.
In my scenario, I have a user control view (named EPayView.xaml) which has a textbox that will accept a phone number. The view has a corresponding viewmodel (named EPayViewModel.cs). In the MainWindow.xaml, I have a user control (floating virtual keyboard) which is derived from namespace controls WpfKb.Controls. The MainWindow.xaml also has a corresponding viewmodel (named MainViewModel.cs)
Having said that, I have done research on how to use attached dependency properties which lead me to this solution. Set focus on textbox in WPF from view model (C#) which I believe this is where I could bind the property IsFocused in the textbox of EPayView.xaml.
Below are the codes that I have already incorporated in my solution.
EpayView.xaml (textbox xaml markup)
<TextBox Text="{Binding PhoneNo}" Grid.Row="5" Margin="10,0,10,0" VerticalContentAlignment="Center" FontSize="12" x:Name="Email" behaviors:FocusExtension.IsFocused="{Binding IsFocused, Mode=TwoWay}"/>
MainWindow.xaml (xaml markup)
<Window x:Class="SmartPole540.View.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:WpfKb.Controls;assembly=SmartPole.WpfKb"
xmlns:wpf="clr-namespace:WebEye.Controls.Wpf;assembly=WebEye.Controls.Wpf.WebCameraControl"
xmlns:utilities="clr-namespace:SoltaLabs.Avalon.Core.Utilities;assembly=SoltaLabs.Avalon.Core"
xmlns:userControls="clr-namespace:SoltaLabs.Avalon.View.Core.UserControls;assembly=SoltaLabs.Avalon.View.Core"
xmlns:square="clr-namespace:SmartPole.View.Square;assembly=SmartPole.View"
xmlns:view="clr-namespace:SmartPole.View;assembly=SmartPole.View"
Title="CitiPulse"
WindowStartupLocation="Manual"
PreviewMouseLeftButtonDown="Window_PreviewMouseLeftButtonDown"
Name="mainWindow">
<userControls:RollPanel.BottomContent>
<square:SquareView Canvas.Top="1010" DataContext="{Binding DataContext.SquareViewModel,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type userControls:RollPanel}}}"/>
</userControls:RollPanel.BottomContent>
<controls:FloatingTouchScreenKeyboard
x:Name="floatKb" Width="500" Height="250" PlacementTarget="{Binding ElementName=MainGrid}"
Placement="Center" AreAnimationsEnabled="False" Visibility="Visible"
IsOpen="{Binding IsChecked, ElementName=kbButton}"/>
</Window>
In the above code, the user control RollPanel.BottomContent host the EPayView.xaml view inside another view which is RollPanel.xaml
EpayViewModel.cs contains the static class FocusExtension for the IsFocused attached property (refer to this solution - Set focus on textbox in WPF from view model (C#)). And, EPayViewModel.cs already implemented INotifyPropertyChanged which is wrapped inside a concrete class ObservableObject that accepts type of T. This is also same with MainViewModel.cs
public class EPayViewModel : ObservableObject<EPayViewModel>, IPaymentViewModel, IActiveViewModel
{ ... }
public class MainViewModel : ObservableObject<MainViewModel>
{ ... }
As such, my goal is that when the textbox in EPayView.xaml has the focus, the floating virtual keyboard (floatKb) in the MainWindow.xaml will be shown.
I'm stuck on how to proceed (I was thinking if a call to FocusExtension static class in EPayViewModel inside my MainViewModel.cs will suffice?), any help is greatly appreciated.
Cheers,
As AnjumSKhan already said, to react to some event in a MVVM way, you'll have to use Command. Command can be called within an EventTrigger, you will need to add a Reference to System.Windows.Interactvity component.
Let's assume you have a simple View and View Model and you need to show this View when the TextBox in a MainWindow got focus.
View (NewWindow.xaml)
<Window x:Class="My.NewWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="NewWindow" Height="300" Width="300">
<TextBlock Text="{Binding Message}"/>
View Model
public class NewWindowViewModel
{
private string _message;
public string Message
{
get { return _message; }
set { _message = value; }
}
}
You also have a MainWindow, it is a main view for an app and it contains the target TextBox. You may see that there is an EventTrigger added to the TextBox and it has a property InvokeCommandAction which is binded to the MainWindowViewModel's command called ShowCommand.
Main Window
<Window x:Class="My.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:Interactivity="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" Title="MainWindow" Height="350" Width="525">
<TextBox Height="40" Text="{Binding Text}">
<Interactivity:Interaction.Triggers>
<Interactivity:EventTrigger EventName="GotFocus">
<Interactivity:InvokeCommandAction Command="{Binding ShowCommand}"/>
</Interactivity:EventTrigger>
</Interactivity:Interaction.Triggers>
</TextBox>
In the Show method of MainWindowViewModel NewWindow view is created and got new NewWindowViewModel instance as a DataContext. RelayCommand class is presented in my answer to this question
MainWindowViewModel
public class MainWindowViewModel
{
private string _text;
public string Text
{
get { return _text; }
set { _text = value; }
}
private ICommand _increaseCommand;
public ICommand ShowCommand
{
get
{
if (_increaseCommand == null)
{
_increaseCommand = new RelayCommand(
p => true,
Show);
}
return _increaseCommand;
}
}
private void Show(object obj)
{
var w = new NewWindow();
var nvm = new NewWindowViewModel();
nvm.Message = "Test";
w.DataContext = nvm;
w.Show();
}
}
What is left is to create a new MainWindowViewModel and setup a DataContext for MainWindow.
public MainWindow()
{
InitializeComponent();
var mvm = new MainWindowViewModel();
mvm.Text = "Focus me!";
DataContext = mvm;
}
Hope it will help.

Binding UserControl Dependency Property and MVVM

I have a MainWindow containing a UserControl, both implemented in MVVM-pattern.
The MainWindowVM has properties that I want to bind to properties in the UserControl1VM. But this doesn't work.
Here's some code (the viewmodels use some kind of mvvm-framework that implement the INotifyPropertyChanged in a ViewModelBase-class but that's hopefully no problem):
MainWindow.xaml:
<Window x:Class="DPandMVVM.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:DPandMVVM"
Title="MainWindow" Height="300" Width="300">
<Grid>
<local:UserControl1 TextInControl="{Binding Text}" />
</Grid>
</Window>
CodeBehind MainWindow.xaml.cs:
using System.Windows;
namespace DPandMVVM
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowVM();
}
}
}
MainWindow-ViewModel MainWindowVM.cs:
namespace DPandMVVM
{
public class MainWindowVM : ViewModelBase
{
private string _text;
public string Text { get { return _text; } }
public MainWindowVM()
{
_text = "Text from MainWindowVM";
}
}
}
And here the UserControl1.xaml:
<UserControl x:Class="DPandMVVM.UserControl1"
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"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TextBlock Text="{Binding TextInTextBlock}" />
</Grid>
</UserControl>
The Codebehind UserControl1.xaml.cs:
using System.Windows.Controls;
namespace DPandMVVM
{
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
DataContext = new UserControl1VM();
}
}
}
And the Viewmodel UserControl1VM.cs:
using System.Windows;
namespace DPandMVVM
{
public class UserControl1VM : DependencyObject
{
public UserControl1VM()
{
TextInControl = "TextfromUserControl1VM";
}
public string TextInControl
{
get { return (string)GetValue(TextInControlProperty); }
set { SetValue(TextInControlProperty, value); }
}
public static readonly DependencyProperty TextInControlProperty =
DependencyProperty.Register("TextInControl", typeof(string), typeof(UserControl1VM));
}
}
With this constellation the DP cannot be found in MainWindow.xaml.
What am I doing wrong?
First of all you want DependencyProperty TextInControl to be declared inside UserControl1 if you want to bind it from outside.
Move the declaration of DP inside of UserControl1.
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
public string TextInControl
{
get { return (string)GetValue(TextInControlProperty); }
set { SetValue(TextInControlProperty, value); }
}
public static readonly DependencyProperty TextInControlProperty =
DependencyProperty.Register("TextInControl", typeof(string),
typeof(UserControl1));
}
Second you have externally set DataContext of UserControl to UserControl1VM,
public UserControl1()
{
InitializeComponent();
DataContext = new UserControl1VM(); <-- HERE (Remove this)
}
So WPF binding engine looking for property Text in UserControl1VM instead of MainWindowVM. Remove setting DataContext and update XAML of UserControl1 to this:
<UserControl x:Class="DPandMVVM.UserControl1"
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"
d:DesignHeight="300" d:DesignWidth="300"
x:Name="userControl1">
<Grid>
<TextBlock Text="{Binding TextInTextBlock, ElementName=userControl1}" />
</Grid>
</UserControl>
Bind DP using ElementName by setting x:Name on UserControl.
UPDATE
In case you want to have ViewModel intact for UserControl, you have to update binding in MainWindow. Explicitly tell WPF binding engine to look for property in MainWindow's DataContext using ElementName in binding like this:
<local:UserControl1 TextInControl="{Binding DataContext.Text,
ElementName=mainWindow}" />
For this you need to set x:Name="mainWindow" on window root level.
The XAML of your control right now reference the property TextInTextBlock via the DataContext which in turn "Points" to your main window's view model. Reference the data of the control and you are done (btw do not set the DataContext for that reason - the binding won't work any more):
<UserControl x:Class="DPandMVVM.UserControl1"
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"
d:DesignHeight="300" d:DesignWidth="300"
x:Name="self">
<Grid>
<TextBlock Text="{Binding TextInTextBlock, ElementName=self}" />
</Grid>
</UserControl>
This is how I do UserControls with MVVM and DP binding. It's similar to Rohit's answer but with some slight changes. Basically you need to set the Control's internal view model to be the DataContext of the root container within the UserControl rather than the UserControl itself, that way it will not interfere with DP bindings.
E.g.
UserControl XAML
<UserControl x:Class="DPandMVVM.UserControl1"
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"
d:DesignHeight="300" d:DesignWidth="300"
x:Name="userControl1">
<Grid x:Name="Root">
<TextBlock Text="{Binding TextFromVM}" />
</Grid>
UserControl Code-behind
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
this.ViewModel = new UserControlVM();
}
public UserControlVM ViewModel
{
get { return this.Root.DataContext as UserControlVM ; }
set { this.Root.DataContext = value; }
}
public string TextFromBinding
{
get { return (string)GetValue(TextFromBindingProperty); }
set { SetValue(TextFromBindingProperty, value); }
}
public static readonly DependencyProperty TextFromBindingProperty =
DependencyProperty.Register("TextFromBinding", typeof(string), typeof(UserControl1), new FrameworkPropertyMetadata(null, OnTextBindingChanged));
private static void OnTextBindingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var uc = d as UserControl1;
uc.ViewModel.TextFromVM = e.NewValue as string;
}
}
This means that the control derives it's values from the Root element DataContext which is our ViewModel but the ViewModel can be updated via a DP binding from outside the control (in your case a binding to the parent Window's ViewModel, see below)
Window XAML
<Window x:Class="DPandMVVM.Window1"
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:local="clr-namespace:DPandMVVM"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
x:Name="window1">
<Grid x:Name="Root">
<local:userControl1 TextFromBinding="{Binding TextFromWindowVM}" />
</Grid>
I have a method that I believe is a lot simpler, and probably more true to MVVM.
In the main window XAML:
<myNameSpace:myUserControl DataContext="{Binding Status}"/>
In your main view model (the data context of the main window:
public myUserControlViewModel Status { set; get; }
now you can in the constructor (or whenever you want to instantiate it):
Status = new myUserControlViewModel();
then if you want to set the text property:
Status.Text = "foo";
and make sure you have the binding setup to a property named Text inside your myUserControlViewModel class:
<TextBox Text="{Binding Text}"/>
and make sure the property fires PropertyChanged, of-course.
Plus, if you use Resharper. You can create a Design instance of the UserControl in your XAML so that it can link the bindings and not tell you that the property is never used by doing this:
<UserControl x:Class="myNameSpace.myUserControl"
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:myNameSpace="clr-namespace:myNameSpace"
d:DataContext="{d:DesignInstance myNameSpace.myUserControl}"
mc:Ignorable="d" ...>
This part:
xmlns:myNameSpace="clr-namespace:myNameSpace"
d:DataContext="{d:DesignInstance myNameSpace.myUserControl}"
Here's a possible working solution for you. However, I've noted in a comment above that this will work in code and perhaps (like my situation) will show up as an error (Object Not Found) in the designer:
<local:UserControl1 TextInControl="{Binding DataContext.Text,
Source={x:Reference <<Your control that contains the DataContext here>>}}" />
I'd rather to have a cleaner solution, though, without any designer errors. I wish to find out how to properly bind a dependency property in a user control to a value coming from the window it's contained in. What I'm finding is that whatever I try to do (short of what I showed above), such as using ElementName and/or AncestorType/Level, etc., the debugger complains that it can't find the source and shows that it's looking for the source inside the context of the user control! It's like I can't break out of the user control context when doing Binding logic in the use of that control (other than that "designer-breaking" solution above).
UPDATE:
I noticed that this might not work for you as your situation might force a problem I just noticed if I change my own source to reference the window instead of a control that has the data context. If I reference the window then I end up with a cyclical redundancy. Perhaps you'll figure out a way to use the Source version of the binding that will work okay for you.
I must also add that my situation is probably a bit more complex since my usercontrol is used in the context of a popup.

WPF : Keeping default style when inheriting from ListBoxItem

Hey Guys (and girls if any :)
I needed a different list box selection policy than the one provided by default with WPF listboxes, i.e. beeing able to have an extended selection withou any modifier key.
No problem on that, here is what I did :
class EnhancedCcyPairListBox : ListBox
{
protected override DependencyObject GetContainerForItemOverride()
{
return new CcyPairListBoxItem();
}
}
internal class CcyPairListBoxItem : ListBoxItem
{
protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e)
{
IsSelected = !IsSelected;
e.Handled = true;
}
}
I can't tell if it's the best way to do, but it seems to work exactly as I expected.
Except that... by doing so, I have lost the defaults ListBoxItem style I had before. How can I tell to my derived classes to keep their default style ?
Thank you very much !
Add this above the EnhancedCcyPairListBox declaration
[StyleTypedProperty(Property = "ItemContainerStyle", StyleTargetType = typeof(CcyPairListBoxItem))]
Edit
Add this to the static constructor of CcyPairListBoxItem"
DefaultStyleKeyProperty.OverrideMetadata(
typeof(CcyPairListBoxItem), new FrameworkPropertyMetadata(typeof(CcyPairListBoxItem)));
and in Themes/Generic.xaml add
<Style TargetType="{x:Type CcyPairListBoxItem}"
BasedOn="{StaticResource {x:Type ListBoxItem}}"/>
Because the WPF context of our application is quite special, I wanted to be sure that it was not an issue that was very specific.
So I took the above two classes and imported them in a brand new WPF test project. I created a Themes directory that registered a Resource dictionnary in which I put the Style as NVM mentionned earlier.
Content of Generic.xaml :
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/InheritedListBoxStyling;component/Themes/Styles/TempDic.xaml" />
<!-- Reference here the Resource dictionnary used for your own component -->
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
Content of TempDic.xaml :
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:InheritedListBoxStyling="clr-namespace:InheritedListBoxStyling">
<Style TargetType="{x:Type InheritedListBoxStyling:CcyPairListBoxItem}"
BasedOn="{StaticResource {x:Type ListBoxItem}}"/>
</ResourceDictionary>
Content of Window1.xaml.cs
namespace InheritedListBoxStyling
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
DataContext = this;
for (int i = 0; i < 50; i++)
{
source.Add("toto " + i);
}
l1.ItemsSource = source;
l2.ItemsSource = source;
}
public ObservableCollection<string> source = new ObservableCollection<string>();
}
}
Content of Window1.xaml :
<Window x:Class="InheritedListBoxStyling.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:InheritedListBoxStyling="clr-namespace:InheritedListBoxStyling"
Title="Window1" Height="300" Width="300">
<Grid>
<ListBox x:Name="l1" SelectionMode="Extended"
Margin="0,0,12,12" Height="100"
HorizontalAlignment="Right"
VerticalAlignment="Bottom" Width="120" />
<InheritedListBoxStyling:EnhancedCcyPairListBox
x:Name="l2" SelectionMode="Extended"
Height="100" Margin="12,12,0,0"
VerticalAlignment="Top"
HorizontalAlignment="Left" Width="120" />
</Grid>
</Window>
And here the result :
=> the default styling is not applied and, as in my "real case" issue, the only kind of styling that seems to be applied is the gray selected item style.
Any idea about what's going on or what I'm doing wrong ?

Resources