I have this Custom UserControl which has a List and a Button:
<UserControl x:Class="WpfApplication1.CustomList"
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>
<ListBox Name="listBox1" ItemsSource="{Binding ListSource}" HorizontalAlignment="Right" Width="174" />
<Button Name="ButtonAdd" Content="Add" HorizontalAlignment="Left" Width="101" />
</Grid>
</UserControl>
The code behind has a DependencyProperty of Type IEnumerable and a handler(OnAdd) for the Button:
public partial class CustomList : UserControl
{
public CustomList( )
{
InitializeComponent( );
ButtonAdd.Click += new RoutedEventHandler( OnAdd );
}
private void OnAdd( object sender, EventArgs e )
{
IList<object> tmpList = this.ListSource.ToList( );
Article tmpArticle = new Article( );
tmpArticle .Name = "g";
tmpList.Add(tmpArticle );
ListSource = (IEnumerable<object>) tmpList;
}
public IEnumerable<object> ListSource
{
get
{
return (IEnumerable<object>)GetValue( ListSourceProperty );
}
set
{
base.SetValue(CustomList.ListSourceProperty, value);
}
}
public static DependencyProperty ListSourceProperty = DependencyProperty.Register(
"ListSource",
typeof( IEnumerable<object> ),
typeof( CustomList ),
new PropertyMetadata( OnValueChanged ) );
private static void OnValueChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
{
( (CustomList)d ).ListSource = (IEnumerable<object>)e.NewValue;
}
}
In the Button handler I am trying to add an Article to the ListSource(which is bound to the Articles).
This is the window where I use my UserControl(CustomList):
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:local="clr-namespace:WpfApplication1"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="MainWindow" Height="350" Width="525">
<Grid>
<local:CustomList ListSource="{Binding Articles, Mode=TwoWay}" Margin="80,0,0,0" />
</Grid>
</Window>
When I click the Button, the Articles become null instead of adding an Article in the in the Articles collection. And the ListSource property also becomes null. Am I doing something wrong here? Is this an expected behavior? If yes, what would be a different way of doing this:
Create a Custom Control that will have a List and a Button and the handler for the button will add objects in the List.
There are a lot of issues here but the main one that's causing your problem is that there is probably a type mismatch between the properties that you are trying to use TwoWay Binding on. You didn't list the code for your Articles property, but I assume it's probably something like IEnumerable<Article> or ObservableCollection<Article> and not IEnumerable<object> as is your ListSource property.
When you set up two way Binding and the target value can't be converted back to the source type (i.e. IEnumerable<object> -> ObservableCollection<Article>) the source property (Articles here) will receive a null value, which will then get pushed back through the Binding to the target property (ListSource here).
You can change the type on either side but if you're using them with a TwoWay Binding the types should match.
In general it's a bad idea to use TwoWay Bindings with collections. Instead of copying and replacing the collection instance every time you want to make a change, just add and remove items from one instance. Since that one instance is the same collection on both sides of the (OneWay) Binding the updates will show up on both sides, and if you're using an ObservableCollection you can also get change notifications on either side.
You should also get rid of your OnValueChanged code since it is just resetting the property to the value that was just set on the property.
Related
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
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.
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.
I'm trying to take advantage of Property Value Inheritance from a Window to a UserControl. As far as I understand, you can achieve this by declaring an attached DependencyProperty (in conjunction with the FrameworkPropertyMetadataOptions.Inherits option).
MainWindow.xaml:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" xmlns:my="clr-namespace:WpfApplication1" Name="BobWindow">
<Grid>
<Label Content="MainWindow" />
<TextBox Height="23" HorizontalAlignment="Left" Margin="85,2,0,0" Name="textBox1" VerticalAlignment="Top" Width="120" Text="{Binding Path=Test, ElementName=BobWindow}" />
<my:UserControl1 HorizontalAlignment="Left" Margin="157,108,0,0" x:Name="userControl11" VerticalAlignment="Top" />
</Grid>
</Window>
MainWindow.xaml.cs:
using System;
using System.Windows;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public static readonly DependencyProperty TestProperty = DependencyProperty.RegisterAttached
(
"Test",
typeof(String),
typeof(MainWindow),
new FrameworkPropertyMetadata
(
null,
FrameworkPropertyMetadataOptions.Inherits
)
);
public String Test
{
get { return (String)GetValue(TestProperty); }
set { SetValue(TestProperty, value); }
}
public MainWindow()
{
InitializeComponent();
Test = "Yip!";
}
}
}
UserControl1.xaml:
<UserControl x:Class="WpfApplication1.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" Name="BobControl">
<Grid>
<Label Content="UserControl1" />
<TextBox Height="23" HorizontalAlignment="Left" Margin="85,2,0,0" Name="textBox1" VerticalAlignment="Top" Width="120" Text="{Binding Path=Test, ElementName=BobControl}" />
</Grid>
</UserControl>
UserControl1.xaml.cs:
using System;
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication1
{
public partial class UserControl1 : UserControl
{
public static readonly DependencyProperty TestProperty = DependencyProperty.RegisterAttached
(
"Test",
typeof(String),
typeof(UserControl1),
new FrameworkPropertyMetadata
(
null,
FrameworkPropertyMetadataOptions.Inherits
)
);
public String Test
{
get { return (String)GetValue(TestProperty); }
set { SetValue(TestProperty, value); }
}
public UserControl1()
{
InitializeComponent();
}
}
}
I haven't been able to find an explicit example to achieve this. The use of RegisterAttached in MainWindow and UserControl1 is my best guess. There must be something I'm missing!
UPDATE
I'd like to be able to create my controls in an arbitrary structure, set the value at the top of the tree and have the default value trickle down (similar to how DataContext works). Is this possible when TestProperty isn't in a common ancestor class for MainWindow and UserControl1?
Also, I want to avoid referencing the source class, since sometimes it will be a Window but in other cases it might be the host control in Windows Forms. Is this possible?
RESOLVE
I think my confusion stemmed from wanting to use the syntax of a non-attached dependency property to achieve value inheritance. I wanted to use the following xaml:
<Window ... Test="Fred" />
And access the inherited value in UserControl with the following syntax:
string Value = this.Test;
However, according Microsoft's Property Value Inheritance page, if you wish to inherit property values, then it must be through an attached property.
If above the code was re-written properly (declare the property once, with static getter/setter methods) then my xaml would look like this:
<Window ... my:MainWindow.Test="Fred" />
And my code behind in UserControl would look like this:
string Value = MainWindow.GetTest( this );
It seems that you might be misunderstanding what value inheritance means. If you set a dependency property on a control the value of that property will be the same in the controls inside of it. You don't need to redeclare the property itself (that just creates another property that is completely distinct).
An example of inheritance:
<Window ...
xmlns:local="..."
local:MainWindow.Test="Lorem Ipsum">
<Button Name="button"/>
In code you then should be able to get the value on the button and it should be the same as on the window.
var test = (string)button.GetValue(MainWindow.TestProperty);
// test should be "Lorem Ipsum".
The mistake you made here is to declare the property twice. Just declare it in MainWindow, not also in UserControl1. Then declare static getter and setter methods like this in MainWindow:
public static string GetTest(DependencyObject obj)
{
return (String)obj.GetValue(TestProperty);
}
public static void SetTest(DependencyObject obj, string value)
{
obj.SetValue(TestProperty, value);
}
Get more info here about custom attached properties.
Now when a UserControl1 is somewhere in the element tree in MainWindow, try to do something like the following with UserControl1 after it has been initialized:
UserControl1 uc = this; // for example in a UserControl1 event handler
string test = MainWindow.GetTest(uc);
EDIT: You could as well define the property in UserControl1 or in any other class, and since it is an attached property, that class does not even have to be derived from DependencyObject.
Just hit a very odd issue with databinding which I cannot seem to get to the bottom of:
Scenario
An MVVM View model data bound to a parent form with two properties
public RelayCommand ClearFilteredCategories { get; private set; }
/// <summary>
/// The <see cref="ClearFilterText" /> property's name.
/// </summary>
public const string ClearFilterTextPropertyName = "ClearFilterText";
private string _clearFilterText = "Clear Filter";
/// <summary>
/// Sets and gets the ClearFilterText property.
/// Changes to that property's value raise the PropertyChanged event.
/// </summary>
public string ClearFilterText
{
get
{
return _clearFilterText;
}
set
{
if (_clearFilterText == value)
{
return;
}
_clearFilterText = value;
RaisePropertyChanged(ClearFilterTextPropertyName);
}
}
Then I have a User control with Two dependency Properties, thus:
public partial class ClearFilterButton : UserControl
{
public ClearFilterButton()
{
// Required to initialize variables
InitializeComponent();
}
public string ClearFilterString
{
get { return (string)GetValue(ClearFilterStringProperty); }
set { SetValue(ClearFilterStringProperty, value); }
}
public RelayCommand ClearFilterAction
{
get { return (RelayCommand)GetValue(ClearFilterActionProperty); }
set { SetValue(ClearFilterActionProperty, value); }
}
public static readonly DependencyProperty ClearFilterStringProperty =
DependencyProperty.Register("ClearFilterString", typeof(string), typeof(ClearFilterButton), new PropertyMetadata("", ClearFilterString_PropertyChangedCallback));
public static readonly DependencyProperty ClearFilterActionProperty =
DependencyProperty.Register("ClearFilterAction", typeof(RelayCommand), typeof(ClearFilterButton), new PropertyMetadata(null, ClearFilterAction_PropertyChangedCallback));
private static void ClearFilterString_PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//empty
}
private static void ClearFilterAction_PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//empty
}
}
and User Control XAML:
<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:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" xmlns:GalaSoft_MvvmLight_Command="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.WP71"
mc:Ignorable="d"
x:Class="ATTCookBook.ClearFilterButton"
d:DesignWidth="75" d:DesignHeight="75"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid x:Name="LayoutRoot" Background="Transparent">
<Button HorizontalAlignment="Center" VerticalAlignment="Center" BorderBrush="{x:Null}" Width="75" Height="75">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<GalaSoft_MvvmLight_Command:EventToCommand Command="{Binding ClearFilterAction, Mode=TwoWay}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<Button.Background>
<ImageBrush Stretch="UniformToFill" ImageSource="/icons/appbar.refresh.rest.png"/>
</Button.Background>
</Button>
<TextBlock HorizontalAlignment="Center" Margin="0,0,0,8" TextWrapping="Wrap" Text="{Binding ClearFilterString, Mode=TwoWay}" VerticalAlignment="Bottom" FontSize="13.333" Height="18" Width="0" d:LayoutOverrides="VerticalAlignment"/>
</Grid>
Now when I add this User Control to the Main Page and databind the two View Model Properties through to the User control it gets very weird:
<local:ClearFilterButton Height="Auto" Width="Auto" ClearFilterAction="{Binding ClearFilteredCategories, Mode=TwoWay}" ClearFilterString="{Binding ClearFilterText, Mode=TwoWay}"/>
Because although the Databinding statements above seem fine, the Binding errors with:
System.Windows.Data Error: BindingExpression path error: 'ClearFilteredCategories' property not found on 'ATTCookBook.ClearFilterButton' 'ATTCookBook.ClearFilterButton' (HashCode=126600431). BindingExpression: Path='ClearFilteredCategories' DataItem='ATTCookBook.ClearFilterButton' (HashCode=126600431); target element is 'ATTCookBook.ClearFilterButton' (Name=''); target property is 'ClearFilterAction' (type 'GalaSoft.MvvmLight.Command.RelayCommand')..
System.Windows.Data Error: BindingExpression path error: 'ClearFilterText' property not found on 'ATTCookBook.ClearFilterButton' 'ATTCookBook.ClearFilterButton' (HashCode=126600431). BindingExpression: Path='ClearFilterText' DataItem='ATTCookBook.ClearFilterButton' (HashCode=126600431); target element is 'ATTCookBook.ClearFilterButton' (Name=''); target property is 'ClearFilterString' (type 'System.String')..
Which seems to indicate the View Model is trying to find the parent properties in the child user control?
I do not understand why this could be because I have set a Relative Data Context within the child user control to avoid this and the binding should be passing through the two dependency properties.
I was hoping to make the user control more generic later but cannot seem to even get it working in a basic fashion
Quick call out to you Silverlight Binding masters :D
In your UserControl you are resetting the Datacontext so now the expression
ClearFilterAction="{Binding ClearFilteredCategories, Mode=TwoWay}"
is not relative to the Page where you are using the UserControl but to the UserControl itself. You can encounter the same problem with ItemTemplate datacontext when you want to reference a common command declared on the VM of the Page and not on the object data bound to the template.
You can use a proxy:
ClearFilterAction="{Binding Source={StaticResource
DataContextProxy},Path=DataSource.ClearFilteredCategories}"
The proxy is declared as a Resource:
<Resources:DataContextProxy x:Key="DataContextProxy" />
and the code of the DataProxy class:
public class DataContextProxy : FrameworkElement
{
public DataContextProxy()
{
this.Loaded += new RoutedEventHandler(DataContextProxy_Loaded);
}
void DataContextProxy_Loaded(object sender, RoutedEventArgs e)
{
var binding = new Binding();
if (!String.IsNullOrEmpty(BindingPropertyName))
{
binding.Path = new PropertyPath(BindingPropertyName);
}
binding.Source = this.DataContext;
binding.Mode = BindingMode;
this.SetBinding(DataContextProxy.DataSourceProperty, binding);
}
public Object DataSource
{
get { return (Object)GetValue(DataSourceProperty); }
set { SetValue(DataSourceProperty, value); }
}
public static readonly DependencyProperty DataSourceProperty =
DependencyProperty.Register("DataSource", typeof(Object), typeof(DataContextProxy), null);
public string BindingPropertyName { get; set; }
public BindingMode BindingMode { get; set; }
}
Move the assignment of the data context to usercontrol's root DataGrid.
<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:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" xmlns:GalaSoft_MvvmLight_Command="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.WP71"
mc:Ignorable="d"
x:Class="ATTCookBook.ClearFilterButton"
d:DesignWidth="75" d:DesignHeight="75">
<Grid x:Name="LayoutRoot" Background="Transparent" DataContext="{Binding Parent, RelativeSource={RelativeSource Self}}" >
<Button HorizontalAlignment="Center" VerticalAlignment="Center" BorderBrush="{x:Null}" Width="75" Height="75">
Resolved in the end with some thanks to the above, however the answer was a lot simpler.
All I had to do was remove any mention of Modes (remove Mode="TwoWay") and remove the DataContext setting from the user control and it just worked.
Just goes to show it's very easy to over-engineer a solution.
I Suspect by adding the Mode setting it was trying to pass the datacontext through the binding and that what it was throwing up (not a very helpful error message)
Hope this helps others. Just keep it simple and work up from there.
For those that are interested, I was basing the implementation from this very useful post - Silverlight UserControl Custom Property Binding