Changing Style via data binding depending on CheckBox state - wpf

In one of my WPF user controls I have a check box. If the check box is not checked, I would like to use the following:
<vf:DataSeries Style="{StaticResource dataSeriesQuickLine}" ... >
However, if it is checked, I would like to use the following:
<vf:DataSeries Style="{StaticResource dataSeriesLine}" ... >
Is there a way I can bind the Style to the check box control to use the styling I want?
Thanks.

Yes, you could bind to IsChecked and use a Binding.Converter which has properties for the styles and returns either depending on the input value.
You could use a generic boolean converter:
<vc:BooleanConverter x:Key="StyleConverter"
TrueValue="{StaticResource Style1}"
FalseValue="{StaticResource Style2}"/>
public class BooleanConverter : IValueConverter
{
public object TrueValue { get; set; }
public object FalseValue { get; set; }
// In Convert cast the value to bool and return the right property
}

Add following namespaces to your xaml:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
Set the default style on your control to Style2. Then
assign a name to your control and
add following trigger and action somewhere in your xaml (e.g. before you close your vf:DataSeries tag):
<i:Interaction.Triggers>
<ei:DataTrigger
Binding="{Binding ElementName=yourCheckboxName, Path=IsChecked}"
Value="True">
<ei:ChangePropertyAction TargetName="yourControlName"
PropertyName="Style"
Value="{StaticResource Style1}"/>
</ei:DataTrigger>
</i:Interaction.Triggers>

Related

Why do I need to specify ElementName and DataContext in a binding?

To familiarize myself with WPF and MVVM concepts I built a visual representation of a Sudoku board.
My (simplified) setup looks like this (no custom code-behind in views anywhere):
I have a MainWindow.xaml:
<Window x:Class="Sudoku.WPF.MainWindow">
<Window.DataContext>
<models:MainWindowViewModel/>
</Window.DataContext>
<ctrl:SudokuBoard DataContext="{Binding Path=GameViewModel}"/>
</Window>
My MainWindowViewModel:
class MainWindowViewModel
{
public MainWindowViewModel()
{
IGame g = new Game(4);
this.GameViewModel = new GameViewModel(g);
}
public IGameViewModel GameViewModel { get; private set; }
}
SudokuBoard is a UserControl. Its DataContext is set to GameViewModel as per above.
Relevant parts of GameViewModel, Elements is populated in the ctor, Possibilities is set via a command:
public IList<CellViewModel> Elements { get; private set; }
private bool _showPossibilities;
public bool ShowPossibilities
{
get { return _showPossibilities; }
set
{
_showPossibilities = value;
OnPropertyChanged();
}
}
In SudokuBoard.xaml I have:
<ItemsControl x:Name="SudokuGrid" ItemsSource="{Binding Path=Elements}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl Style="{StaticResource ToggleContentStyle}"
Content="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Elements is a collection of CellViewModels generated in the constructor of GameViewModel.
Now to the question: my ToggleContentStyle as defined in <UserControl.Resources>:
<Style x:Key="ToggleContentStyle" TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=DataContext.ShowPossibilities, ElementName=SudokuGrid}" Value="False">
<Setter Property="ContentTemplate" Value="{StaticResource valueTemplate}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=DataContext.ShowPossibilities, ElementName=SudokuGrid}" Value="True">
<Setter Property="ContentTemplate" Value="{StaticResource possibilityTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
(both ContentTemplates just show other properties of a single CellViewModel in different representations)
Question 1: I have to explicitly reference DataContext in order to get to the ShowPossibilities property. If I leave it out, so that Path=ShowPossibilities, I get a UniformGrid with the ToString() representation of CellViewModel. My assumption is that that is because the style is referenced from the ItemTemplate, with it's binding set to a single CellViewModel. Is that assumption valid?
Question 2: When I omit the ElementName part, I also get the ToString() representation of CellViewModel. Now I'm really confused. Why is it needed?
Datacontext is a dependency property which is marked as inherits. That means its inherited down the visual tree.
When you bind the default place it's going to look for a source is in the datacontext.
This is the simple situation.
Say you have a window and that has datacontext set to WindowViewmodel and stick a textbox in that Window. You bind it's Text to FooText. This means the textbox goes and looks for a FooText property in that instance of WindowViewmodel.
All pretty simple so far.
Next...
You use elementname.
What that does is says go and take a look at this element. Look for a property on that. If you did that with our textbox above then it would expect a dependency property FooText on whatever you point it to.
Datacontext is a dependency property.
And when you do:
"{Binding FooProperty
This is shorthand for:
"{Binding Path=FooProperty
Where FooProperty is a property path, not =just the name of a property.
Which is maybe worth googling but means you can use "dot notation" to walk down the object graph and grab a property on an object ( on an object.... ).
Hence DataContext.Foo or Tag.Whatever ( since tag is another dependency property a control will have ).
Let's move on to some other complications.
The datcontext is inherited down the visual tree but there's a few of gotchas here. Since
some things look like they're controls but are not ( like datagridtextcolumn ). Templated things can be tricky. Itemscontrols are a kind of obvious and relevent special case.
For an itemscontrol, the datacontext of anything in each row is whichever item it's presented to from the itemssource. Usually you're binding an observablecollection of rowviewmodel to that itemssource. Hence ( kind of obviously ) a listbox or datagrid shows you the data from each rowviewmodel you gave it in each row.
If you then want to go get a property is not in that rowviewmodel you need to tell it to look somewhere else.
When you specify an element in Binding (eg ElementName=SudokuGrid), the Path has to refer to any property of that element. Because this element is a wpf control, DataContext is one of it's properties but ShowPossibilities isn't. So if you do just Path=ShowPossibilities it will not be able to find that path at all.
If you don't specify element in Binding at all then it defaults to the DataContext associated with the control. If the associated DataContext doesn't have the property ShowPossibilities it will not be able to find it.
PS: If you want to debug wpf UI to see what the DataContext is at run-time you could use utility like Snoop.

Bind to Datacontext property in button trigger?

I understand my problem however I'm looking for advice on a solution:
<Button.Style>
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource ButtonStyle}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="MouseOverControl" Value="True" />
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
I'm trying to bind to a property inside my datacontext, basically I want to tell the DC when a control in my UI has the mouse over it. I think I'll only need this for two buttons and it doesn't matter which one it's over, therefore I don't need to bother with a complicated solution (I hope).
Problem is at the moment it's looking for Button.MouseOverControl which obviously doesn't exist, I'd like to understand how you might go about accessing the DC instead.
Thanks!
EDIT: So I've attempted to go down the attached property/behavior route, here is what I have so far:
public static class MouseBehaviour
{
public static readonly DependencyProperty MouseOverProperty
= DependencyProperty.RegisterAttached(
"MouseOver",
typeof(bool),
typeof(MouseBehaviour),
new FrameworkPropertyMetadata(false,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
MouseOverBindingPropertyChanged));
public static bool GetMouseOver(DependencyObject obj)
{
return (bool)obj.GetValue(MouseOverProperty);
}
public static void SetMouseOver(DependencyObject obj, bool value)
{
obj.SetValue(MouseOverProperty, value);
}
private static void MouseOverBindingPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var element = d as FrameworkElement;
if (element != null)
{
// Unsure about this method..
}
}
}
Also I've added this to my button to try and link them, it appears to work:
ex:MouseBehaviour.MouseOver="{Binding MouseOverControl}"
However nothing happens, this is because I think it's working the wrong way around at the moment, so it is expecting my DC property to change but I want it so the MouseOverControl in my DC reflects the value of the IsMouseOver property of my button. Would it be as simple as:
SetMouseOver(element, element.IsMouseOver);
Or something similar?
First thing come to mind is binding IsMouseOver property to MouseOverControl property in viewmodel directly without trigger. Unfortunately that scenario is not supported.
One possible workaround to address that limitation is using event that is raised whenever IsMouseOver property changed to trigger method/command in viewmodel. We can do that using interaction triggers. Since IsMouseOverChanged event doesn't exist, we can use 2 events (MouseEnter and MouseLeave) as alternative.
<Button>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseEnter">
<ei:CallMethodAction MethodName="MouseEnter" TargetObject="{Binding}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeave">
<ei:CallMethodAction MethodName="MouseLeave" TargetObject="{Binding}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
Then have corresponding methods in viewmodel :
public void MouseEnter()
{
MouseOverControl = true;
}
public void MouseLeave()
{
MouseOverControl = false;
}
Another possible way is by creating attached behavior for MouseOver so that you can bind it to viewmodel's property as demonstrated in this blog post.
So I ended up solving this by adding my own action to update a property because CallMethodAction is only available in Blend 4 which at the time I'm unable to use.
This question helped me considerably: Setting a property with an EventTrigger
In particular I'd like to direct you to user Neutrino's answer on that page (Here), the only part I needed to change was the XAML implementation:
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseEnter">
<ex:SetPropertyAction PropertyName="MouseOverControl" TargetObject="{Binding}"
PropertyValue="true" />
</i:EventTrigger>
<i:EventTrigger EventName="MouseLeave">
<ex:SetPropertyAction PropertyName="MouseOverControl" TargetObject="{Binding}"
PropertyValue="false"/>
</i:EventTrigger>
</i:Interaction.Triggers>
Quick explanation is whenever the mouse enters a button I've added these triggers to, it sets a property in my viewmodel/datacontext to mirror this, perfect! Credit to har07 for providing several alternate solutions which also would have worked in different situations (and if I could figure out attached behaviors!!)

WPF Radio Button Fires Converter in strange order

I am writing a simple program using the MVVM Model on WPF. Basicly when the user clicks a radio button in a group of radio buttons, it will update a property in the View Model with the new Account number. The problem is, when I click a different button the converter is called for the new button IsChecked Binding, and then after that it runs the converter for the previous button IsChecked binding(for losing its checked status).
This is causing a problem, since the new button is updating the value of the property with the correct account number, and then when the old button calls the converter, it gets converted back to the old value. I have hacked it to work by adding a static variable to the class, and if the IsChecked property is false, just return the value in the static variable. Does anyone have a better solution for Short Circuting the Converter Call on the box that loses its checked status. Code is below:
Converter:
class RadioToAccountConverter : IValueConverter
{
static string myValue; //HACK TO MAKE IT WORK
object IValueConverter.Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return parameter.ToString();
}
object IValueConverter.ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if ((bool)value)
{
myValue = parameter.ToString(); // Hack to make it work
return parameter.ToString();
}
return myValue; // Hack to make it work
}
}
XAML:
<RadioButton Foreground="HotPink"
Grid.Column="0"
Content="6087721"
Tag="6087721"
IsChecked="{Binding Account, Converter={StaticResource Radio2Value}, Mode=OneWayToSource, ConverterParameter=6087721}">
</RadioButton>
<RadioButton Foreground="HotPink"
Grid.Column="1"
Content="BFSC120"
IsChecked="{Binding Account, Converter={StaticResource Radio2Value}, Mode=OneWayToSource, ConverterParameter='BFSC120'}">
</RadioButton>
<RadioButton Foreground="HotPink"
Grid.Column="2"
Content="BFSC121"
IsChecked="{Binding Account, Converter={StaticResource Radio2Value}, Mode=OneWayToSource, ConverterParameter=BFSC121}">
</RadioButton>
<RadioButton Foreground="HotPink"
Grid.Column="3"
Content="BFSC206"
IsChecked="{Binding Account, Converter={StaticResource Radio2Value}, Mode=OneWayToSource, ConverterParameter=BFSC206}">
</RadioButton>
Property:
public const string AccountPropertyName = "Account";
private string _account;
/// <summary>
/// Sets and gets the Account property.
/// Changes to that property's value raise the PropertyChanged event.
/// </summary>
public string Account
{
get
{
return _account;
}
set
{
if (_account == value)
{
return;
}
RaisePropertyChanging(AccountPropertyName);
_account = value;
RaisePropertyChanged(AccountPropertyName);
}
}
Any Help Is Greatly Appreciated.
Based on what I understand, you want to give users the ability to select from a list of account numbers. You're choice of presentation (view) is a group of radio buttons.
If that is true, the key part is this: you want to give users the ability to select from a list of account numbers. This means that the control you should use is a ListBox, since users should select one of the appropriate values. Now, since you are looking to use radio buttons visually, you simply have to supply an alternative ItemsSource.ItemContainerStyle.
XAML:
<ListBox ItemsSource="{Binding AccountNumbers, Mode=OneWay">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<RadioButton Content="{Binding}" IsChecked="{Binding IsSelected, RelativeSource={x:Static RelativeSource.TemplatedParent}}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
Note that you'll need to add another property on your ViewModel (I named it AccountNumbers). For example:
public IReadOnlyCollection<string> AccountNumbers { ... }
Of course, the underlying collection can be a observable if you need it to be, but that's really up to you.
If you define a GroupName on each RadioButton, WPF will manage the IsChecked states for you.
You could bind the state with a {Binding SomeProperty, Mode=OneWayToSource} if you want the ViewModel to be aware of state.
One way to approach this would be to bind each RadioButton's IsChecked property to the whole ViewModel, just bind it to something like
IsChecked="{Binding WholeViewModel, Mode=OneWayToSource, Converter={StaticResource MyRadioButtonConverter}, ConverterParameter=SomethingReallyUnique}"
...where the public property WholeViewModel is a property that does a return this; in the getter. This would let you have access to the ViewModel and enough information to query the ViewModel to see if the radiobutton should be checked or not. But, only do this if the GroupName DependencyProperty doesn't somehow give you what you want.
To process the clicking on the buttons, then, to actually change the ViewModel state, you'd implement an ICommand in your ViewModel and bind the Command property of the RadioButton to {Binding ClickedCommand} and define a CommandParameter with any string you want. This approach will guarantee a one-way relationship to the IsChecked state, preventing the thing you're describing, I think.
I'll work up a code sample if you think you'd like one.

WPF: dynamic menu with buttons

I have a simple wpf-mvvm application where you can create and edit records. Something like this:
If you create a new record there are a "create" and "cancel" button.
If you edit an existing record there is a "edit", "delete" and "cancel" button.
I don't want to use two different form. I would like to use one, and create a dynamic menu, where I can choose which buttons are visible.
The xaml now is something like this:
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button MinWidth="93" Command="{Binding CreateCommand}>
Create
</Button>
<Button MinWidth="93" Command="{Binding EditCommand}>
Edit
</Button>
<Button MinWidth="93" Command="{Binding DeleteCommand}>
Delete
</Button>
<Button MinWidth="93" Command="{Binding CancelCommand}>
Cancel
</Button>
</StackPanel>
What is the best way to do this?
I've had a similar situation. There are two options (at least, as always):
Use the CanExecute method of the commands and let them return true or false, depending on the type of record you want to edit. The CanExecute value toggles the IsEnabled property of the control it is bound to. This means, if you want to hide the control, you need to 'push' the IsEnabled value to the Visibility value, for example by using a style trigger.
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Visibility" Value="Hidden"/>
</Trigger>
</Style.Triggers>
That would be the standard approach, I guess, and probably makes sense for you.
I had more dynamic circumstances and wanted to create the buttons dynamically. This can easily be done, when you define a Collection of CommandViewModels in your ViewModel. The CommandViewModel can have a name property which you display in a button and the command you want to execute. Then you can use this collection to populate an ItemsControl with buttons. Probably a bit of a overkill for your situation, but it refers to the title of your question and maybe you find it interesting and can use it at some point.
In short, the ViewModels:
public class CommandViewModel : ViewModelBase
{
public ICommand Command { get { return ... } }
public string Name { get; set; }
}
public class MainViewModel : ViewModelBase
{
...
ObservableCollection<CommandViewModel> Commands { get; private set; }
public MainViewModel()
{
Commands = new ObservableCollection<CommandViewModel>();
// Creates the ViewModels for the commands you want to offer
PopulateCommands();
}
}
And in the XAML looks something like:
<ItemsControl ItemsSource="{Binding Commands}"}>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Command="{Binding Command}" Content="{Binding Name}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
That makes a dynamic menu...
Have fun.
If you are using MVVM, then you have a ViewModel which is your DataContext which contains the Create, edit, delete, and cancel commands.
Have your ViewModel have an instance of a record. If you edit it, pass in the instance to be edited. Else for creating a record set that to null.
Create your commands and have the CanExecute functionality check if the record that was passed in was null or not. (null represents Creating a new record, else editing). If you set the CanExecute of your commands to false, the buttons bound to it will automatically be disabled.

Setter but not for Style in WPF?

I know how Triggers with Setters work in WPF, and I know that Setters can only change Style properties. Is there something equivalent to the Setter for non-Style properties? I really want to be able to change a property on custom object that is instantiated in XAML. Any ideas?
Edit: While Setters can update any dependency property, I am attempting to do this within an EventTrigger, which I forgot to specify. There is this workaround, but I'm not sure if it is really best practice. It uses storyboards and ObjectAnimationUsingKeyFrames. Is there anything wrong with this?
Using Interactivity from the Blend SDK you can do this in XAML, you only need to create a TriggerAction which sets the property.
Edit: There already is such an action in another namespace: ChangePropertyAction
In XAML you can use this namespace: http://schemas.microsoft.com/expression/2010/interactions
Tested example:
public class PropertySetterAction : TriggerAction<Button>
{
public object Target { get; set; }
public string Property { get; set; }
public object Value { get; set; }
protected override void Invoke(object parameter)
{
Type type = Target.GetType();
var propertyInfo = type.GetProperty(Property);
propertyInfo.SetValue(Target, Value, null);
}
}
<StackPanel>
<StackPanel.Resources>
<obj:Employee x:Key="myEmp" Name="Steve" Occupation="Programmer"/>
</StackPanel.Resources>
<TextBlock>
<Run Text="{Binding Source={StaticResource myEmp}, Path=Name}"/>
<Run Name="RunChan" Text=" - "/>
<Run Text="{Binding Source={StaticResource myEmp}, Path=Occupation}"/>
</TextBlock>
<Button Content="Demote">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<t:PropertySetterAction Target="{StaticResource myEmp}"
Property="Occupation"
Value="Coffee Getter"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</StackPanel>
Note that with Value being an object default ValueConversion will not take place, if you enter a value as an attribute (Value="Something") it will be interpreted as a string. To set an int for example you can do this:
xmlns:sys="clr-namespace:System;assembly=mscorlib"
<t:PropertySetterAction Target="{StaticResource myEmp}"
Property="Id">
<t:PropertySetterAction.Value>
<sys:Int32>42</sys:Int32>
</t:PropertySetterAction.Value>
</t:PropertySetterAction>
Have you declared the properties that you want to set as dependency properties? I can't find the project I did this in, but I am pretty sure that is what fixed it for me.
I tried implementing something pretty simple, and got the following:
The property "Type" is not a DependancyProperty. To be used in markup, non-attached properties must be exposed on the target type with and accessible instance property "Type". For attached properties the declaring type must provide static "GetType" and "SetType" methods.
Here is a dependancy property registration example from another project of mine:
Public Shared TitleProperty As DependencyProperty = DependencyProperty.Register("Title", GetType(String), GetType(SnazzyShippingNavigationButton))
In the above example, SnazzyShippingNavigationButton is the Class Name of which the property is a member.
And the associated property declaration:
<Description("Title to display"), _
Category("Custom")> _
Public Property Title() As String
Get
Return CType(GetValue(TitleProperty), String)
End Get
Set(ByVal value As String)
SetValue(TitleProperty, value)
End Set
End Property
Description and Category attributes only really apply to the IDE designer property grid display.

Resources