User control parameter in wpf mvvm - wpf

I want to create a user control with a bool parameter that defines a dynamic behavior, using MVVM pattern, so I can use the user control in another view that way :
<local:MyUserControl BoolParam={Binding aBoolBinding} />
About the coding of the user control, the xaml should use the value of BoolParam to do something like this :
...
<TextBox>
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Trigger>
<DataTrigger Binding="{referenceToBoolParam}" Value="False" >
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
...
Am I supposed to define a property
public bool BoolParam { get; set; }
in the code-behind of the user control, and to code the logic associated to this parameter in the ViewModel of the user control ?
Actually I am a bit confused about it, what is the good practice ?

BoolParam should be a dependency property for you to be able to bind something to it. You define this in the code-behind of MyUserControl:
public bool BoolParam
{
get { return (bool)this.GetValue(BoolParamProperty); }
set { this.SetValue(BoolParamProperty, value); }
}
public static readonly DependencyProperty BoolParamProperty = DependencyProperty.Register(
"BoolParam", typeof(bool), typeof(MyUserControl), new PropertyMetadata(false));
You could then set the DataContext of the UserControl to an instance of a view model that contains a public bool property called aBoolBinding and bind to this one as usual.
View Model:
private bool _b;
public bool aBoolBinding
{
get
{
return _b;
}
set
{
_b = value;
NotifyPropertyChanged();
}
}
View:
<local:MyUserControl BoolParam="{Binding aBoolBinding}" />
This is how data binding works. A target property in the views is bound to a source property of a view model.
Edit:
This binds to the BoolParam property of the UserControl from a TextBox style defined in the UserControl:
<TextBox>
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding BoolParam, RelativeSource={RelativeSource AncestorType=UserControl}}" Value="False" >
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>

Related

WPF Concatenated DataTrigger Binding

In my application i'd like to change the Background property of a TextBox when the <Property>IsChanged property of my Model is set to True
I have been able to successfully do this for one of my TextBox controls with the following style:
Notice the hard-coded binding for FirstNameIsChanged in the DataTrigger
<TextBox.Style>
<Style TargetType="TextBox" BasedOn="{StaticResource {x:Type TextBox}}">
<Style.Triggers>
<DataTrigger Binding="{Binding FirstNameIsChanged}" Value="True">
<Setter Property="Background" Value="SteelBlue"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
If my application has a form with 10 or so TextBox controls (FirstName, MiddleName, LastName, Age, Gender etc.) i don't want to copy and paste the above code if i'm only changing the Binding on the DataTrigger for each property.
I've researched using MultiBinding in combination with RelativeSource and StringFormat. I'm trying to create a binding that gets the Tag property of the TextBox and adds "IsChanged" to the name but i have not managed to get it working (I don't see any binding warnings/errors in Visual Studio either)
This is the Style i'm trying to create:
<Style x:Key="TextBoxTestStyle" TargetType="TextBox" BasedOn="{StaticResource {x:Type TextBox}}">
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding StringFormat="\{0\}IsChanged">
<Binding Path="Tag"
RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type TextBox}}" />
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Background" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
Each of the Properties on my Model have their own IsChanged property that i'm using for Change Tracking, this is a quick preview of my Model and how i've made the IsChanged properties:
public class Person
{
public string FirstName { get; set; }
public bool FirstNameIsChanged { get; set; }
public string MiddleName { get; set; }
public bool MiddleNameIsChanged { get; set; }
public string LastName { get; set; }
public bool LastNameIsChanged { get; set; }
public int Age { get; set; }
public bool AgeIsChanged { get; set; }
public DateTime? DateOfBirth { get; set; }
public bool DateOfBirthIsChanged { get; set; }
}
As an example, if i have the following TextBox and i set the Tag property to "FirstName", the style should set the Background property to "Red" if the FirstNameIsChanged property is set to True :
<TextBox x:Name="FirstNameTextBox"
Tag="FirstName"
Margin="3"
Height="23"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Style="{DynamicResource TextBoxTestStyle}"
Text="{Binding FirstName,
Mode=TwoWay,
NotifyOnValidationError=true,
ValidatesOnExceptions=true,
UpdateSourceTrigger=PropertyChanged}"/>
The style (If it's codable) would allow me to set the Tag property on 10 TextBox controls but only have 1 style for all my TextBox controls instead of having to copy and paste the style 10 times. I also want to avoid adding "Boiler plate" code if i decide to add additional properties to my Model in the future such as an IsValid property or an IsEnabled property.
Some ideas i've had:
Can i add "IsChanged" to the string value of the Tag property and use the resulting string in a binding? e.g. Tag("FirstName") + "IsChanged" = "FirstNameIsChanged"
From a Style can i walk up the visual tree and grab the Binding that is already on the Text property and add "IsChanged" to the path? e.g. BindingPath("FirstName") + "IsChanged" = "FirstNameIsChanged"
You shouldn't be comparing to a boolean. Also, to make it Generic, I would make the Tag TextBox-independent, e.g. IsChanged:
<Style x:Key="TextBoxTestStyle" TargetType="TextBox">
<Style.Triggers>
<Trigger Property="Tag" Value="IsChanged">
<Setter Property="Background" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
EDIT
To preserve to Booleans you could use a less generic solution. Since Tag expects a string I added a Converter (you could also create another Dependeny Property expecting a boolean).
<....Resources>
<local:BoolToStringConverter x:Key="BoolToStringConverter"/>
<Style x:Key="TextBoxStyle" TargetType="TextBox">
<Style.Triggers>
<Trigger Property="Tag" Value="True">
<Setter Property="Background" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
</....Resources>
...
<TextBox
x:Name="FirstNameTextBox"
Tag="{Binding FirstNameIsChanged,
Converter={StaticResource BoolToStringConverter}}"
Style="{StaticResource TextBoxStyle}"
...
/>
<TextBox
x:Name="MiddleNameTextBox"
Tag="{Binding MiddleNameIsChanged,
Converter={StaticResource BoolToStringConverter}}"
Style="{StaticResource TextBoxStyle}"
...
/>
...
Without reaching for code-behind (see my comment), this is probably the shortest solution. If you're not using the TextBox Tag Property for anything else, you could also make the Style implicit.

Trigger for custom dependency properties in Style

The problem
I defined a reusable control, MyControl, that extends a TextBox.
I want to set a Trigger to one of its dependency properties.
So I added a style to it, with Triggers.
But if I set the TargetType of the Style to MyControl, I get a XAML warning that 'MyControl' TargetType does not match type of element 'TextBlock'.
And if I set it to TextBlock, I get a compilation error that The member "MyDependencyProperty" is not recognized or is not accessible..
How can I define this style with the triggers?
Sample
C# code-behind
namespace UserControls.Local
{
public partial class MyControl : TextBlock
{
#region Trogdor
public static readonly DependencyProperty TrogdorProperty = DependencyProperty.Register(
"Trogdor", typeof (bool), typeof (MyControl), new PropertyMetadata(default(bool)));
public bool Trogdor
{
get { return (bool) GetValue(TrogdorProperty); }
set { SetValue(TrogdorProperty, value); }
}
#endregion
public MyControl()
{
InitializeComponent();
}
}
}
XAML
<TextBlock
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:UserControls.Local"
mc:Ignorable="d"
Text="BOOP!"
x:Class="UserControls.Local.MyControl">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="Blue"/>
<Style.Triggers>
<Trigger Property="Trogdor" Value="True">
<Setter Property="Foreground" Value="DeepPink"/>
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
The solution I found was to "fully qualify" the dependency property on the binding:
<Trigger Property="local:MyControl.Trogdor" Value="True">
Not sure if you are still looking for a solution, but the answer in this thread worked for me, while yours didn't.
It uses a DataTrigger with a binding on the root element, instead of a Trigger:
<DataTrigger Binding="{Binding Path=Highlight, RelativeSource={RelativeSource AncestorType={x:Type Elements:DataElement}}}" Value="True">
<Setter Property="Control.Background" Value="{DynamicResource EntryBoxHighlightBackground}"/>
</DataTrigger>
With your solution, I press the button and the value of the variable changes but the style trigger doesn't apply the changes, like it was not informed of the change in the variable.

DataContext not binding in Style.Trigger

So I have some code similar to the following: (Forgive any typos-- I tried to simplify in the SO editor for the post)
<my:CustomContentControl>
<my:CustomContentControl.Style>
<Style TargetType="{x:Type my:CustomContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=CurrentView}" Value="MyCustomView">
<Setter Property="Content">
<Setter.Value>
<my:CustomView DataContext="{Binding DataContextForMyCustomView"/>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</m:CustomContentControl.Style>
</my:CustomContentControl>
The problem is that whenever the DataTrigger occurs, the setter does set the Content property to my:CustomView, but it does not bind DataContext. If I move the same code outside of the trigger the DataContext binding works just fine.
Any ideas? If this is a limitation of some sorts, is there any work around?
Update:
I received the following error in the output window:
System.Windows.Data Error: 3 : Cannot find element that provides DataContext. BindingExpression:Path=DataContextForMyCustomView; DataItem=null; target element is 'CustomView' (Name='customView'); target property is 'DataContext' (type 'Object')
The error you posted makes it sound like your custom control is in an object that doesn't have a DataContext, such as a DataGridColumn.Header.
To get around that, you can create a Freezeable object in your .Resources containing the binding you're looking for, then bind your my:CustomView.DataContext to that object
<my:CustomContentControl.Resources>
<local:BindingProxy x:Key="proxy"
Data="{Binding DataContextForMyCustomView, ElementName=MyControl}" />
</my:CustomContentControl.Resources>
...
<my:CustomView DataContext="{Binding Source={StaticResource proxy}}"/>
Here's the code for a sample Freezable object copied from here:
public class BindingProxy : Freezable
{
#region Overrides of Freezable
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
#endregion
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
// Using a DependencyProperty as the backing store for Data.
// This enables animation, styling, binding, etc...
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object),
typeof(BindingProxy), new UIPropertyMetadata(null));
}
Also, you really should use ContentTemplate instead of Content to avoid an exception if more than one object applies that style :)
I solved a similar problem by putting the UserControl into the resources and then changing the Content with that.
e.g. from my own code (different names, same concept)
<ContentControl Grid.Column="1"
Margin="7,0,7,0">
<ContentControl.Resources>
<mapping:Slide11x4MappingView x:Key="Slide11X4MappingView" DataContext="{Binding MappingViewModel}"/>
<mapping:MicrotubeMappingView x:Key="MicrotubeMappingView" DataContext="{Binding MappingViewModel}"/>
</ContentControl.Resources>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding Acquirer.Sorter.TrayType}" Value="{x:Static mapping:TrayType.SLIDES11X4}">
<Setter Property="Content" Value="{StaticResource Slide11X4MappingView}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Acquirer.Sorter.TrayType}" Value="{x:Static mapping:TrayType.VIALS}">
<Setter Property="Content" Value="{StaticResource MicrotubeMappingView}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>

WPF: Binding to a (observable) Dictionary

I'm using this ObservableCollection-Class within my Project: Link
I want to Bind a RibbonMenuButton to a ObservableDictionary<string,bool>:
<r:RibbonMenuButton ItemsSource="{Binding MyDictionary}">
<r:RibbonMenuButton.ItemContainerStyle>
<Style TargetType="{x:Type r:RibbonMenuItem}">
<Setter Property="IsCheckable" Value="true"/>
<Setter Property="Header" Value="{Binding Path=Key}"/>
<Setter Property="IsChecked" Value="{Binding Path=Value}"/>
</style>
</r:RibbonMenuButton.ItemContainerStyle>
</r:RibbonMenuButton>
But I get exceptions because the Value-Properties of the internal IDictionary-KeyValuePairs are readonly. Any Idea how to solve this?
I thought about something like:
<Setter Property="IsChecked" Value="{Binding Source=MyDictionary[{Binding Path=Key}]}"/>
But this won't work 'cause of {Binding} in {Binding}...
This doesn't work, because your dictionary isn't treated as a dictionary but as an IEnumerable<KeyValuePair<string, bool>>. So each RibbonMenuItem is bound to a KeyValuePair<string, bool> with readonly properties Key and Value.
You can do two one things:
1. Use an ObservableCollection<Tuple<string, bool>> instead of the dictionary and bind IsChecked to Item2.
2. Create a little helper class that contains a IsChecked property and change your dictionary to contain that class as the value and bind IsChecked to Value.IsChecked.
I would go with answer two, because the needed changes and possible side effects are smaller.
My answer assumes that you want to have a two way binding on IsChecked. If not, go with the answer of slugster.
WPF binding is two-way by default. Make it one-way and see if that solves your issue.
<r:RibbonMenuButton ItemsSource="{Binding MyDictionary}">
<r:RibbonMenuButton.ItemContainerStyle>
<Style TargetType="{x:Type r:RibbonMenuItem}">
<Setter Property="IsCheckable" Value="true"/>
<Setter Property="Header" Value="{Binding Key, Mode=OneWay}"/>
<Setter Property="IsChecked" Value="{Binding Value, Mode=OneWay}"/>
</style>
</r:RibbonMenuButton.ItemContainerStyle>
</r:RibbonMenuButton>
Here is a reference for you: MSDN Windows Presentation Foundation Data Binding: Part 1 (specifically check the section Binding Mode close to the bottom of the page)
If You want to bind MenuItems to Dictionary<string, bool> without using a helper class, like the accepted answer suggests, here is the minimal-change solution (no need to add anything else):
define a Click event inside the ItemContainerStyle whose ClickEventHandler will update the dicitonary.
declare a dictionary and initialize it inside the UserControl's / Window's constructor
In code:
MainWindow.xaml:
<MenuItem Header="_My settings" ItemsSource="{Binding MySettings}">
<MenuItem.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="IsCheckable" Value="true"/>
<Setter Property="Header" Value="{Binding Key, Mode=OneWay}"/>
<Setter Property="IsChecked" Value="{Binding Value, Mode=OneWay}"/>
<!-- this is the main line of code -->
<EventSetter Event="Click" Handler="MySettings_ItemClick"/>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
// properties...
// Declaration of the dictionary
public Dictionary<string, bool> MySettings{ get; set; }
public MainWindow()
{
InitializeComponent();
// Initialize the dictionary
MySettings = new Dictionary<string, bool>()
{
{ "SettingOne", true}
// Other pairs..
};
}
// other things..
// ClickEvent hanlder
private void MySettings_ItemClick(object sender, RoutedEventArgs e)
{
MenuItem clickedItem = (sender as MenuItem);
MySettings[clickedItem.Header as string] = clickedItem.IsChecked;
}
} // end of MainWindow class
That's it! You're all set!
Credits to slugster and his answer for XAML code for OneWay binding :)
As a general solution to this problem of binding to dictionaries I created an UpdateableKeyValuePair and return that instaed of the usual KeyValuePair. Here is my class:
public class UpdateableKeyValuePair<TKey,TValue>
{
private IDictionary<TKey, TValue> _owner;
private TKey _key;
public UpdateableKeyValuePair(IDictionary<TKey, TValue> Owner, TKey Key_)
{
_owner = Owner;
_key = Key_;
}
public TKey Key
{
get
{
return _key;
}
}
public TValue Value
{
get
{
return _owner[_key];
}
set
{
_owner[_key] = value;
}
}
}

WPF Datatrigger for an item template

I have the following xaml inside a text box element that is part of a combo box item template. The combobox's items source is set to a list of objects that have a boolean property AcceptsInput everything works great but I can't get this trigger to fire do I have to do something else.
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding AcceptsInput}" Value="False" >
<Setter Property="Visibility" Value="Hidden"> </Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
Are you correctly implementing INotifyPropertyChanged in the viewmodel class with the AcceptsInput property?
It should look something like this:
public class MyClass: INotifyPropertyChanged
{
private bool _acceptsInput;
public bool AcceptsInput
{
get { return _acceptsInput; }
set
{
_acceptsInput = value;
OnPropertyChanged("AcceptsInput");
}
}
...
}

Resources