WPF Automation of UIElements - c#

I'm working on implementing a joystick into my WPF app. I would like to simulate clicking on buttons. I can run the events with the code below, but I want to also show the button being clicked in the UI. Is this possible for button and other UI items?
var invokeProv = (IInvokeProvider) (new ButtonAutomationPeer(ButtonHome).GetPattern(PatternInterface.Invoke));
invokeProv?.Invoke();
ButtonHome.RaiseEvent(new RoutedEventArgs(ButtonBase.ClickEvent));
I ended up with this as a possible solution. In the mainwindow class I put...
public static readonly DependencyProperty ButtonPressedProperty =
DependencyProperty.Register("ButtonPressed", typeof(bool), typeof(MainWindow), new PropertyMetadata(null));
added a property...
private bool ButtonPressed
{
get => (bool) GetValue(ButtonPressedProperty);
set => SetValue(ButtonPressedProperty, value);
}
In the mainwindow xaml under the button I put ...
<Button.Style>
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource {x:Type Button}}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}, Path=(windows:MainWindow.ButtonPressed)}" Value="True">
<Setter Property="Background" Value="{StaticResource PressedButtonBackground}"/>
<Setter Property="Foreground" Value="{StaticResource ButtonForeground}"/>
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}, Path=(windows:MainWindow.ButtonPressed)}" Value="False">
<Setter Property="Background" Value="{StaticResource ButtonBackground}"/>
<Setter Property="Foreground" Value="{StaticResource ButtonForeground}"/>
</DataTrigger>
</Style.Triggers>
</Style>
Evaluating my joystick buttons i just used ButtonPressed = gamepadButtons[3];

the only thing that comes to my mind is if I was on your place I'd simply create data trigger on the xaml side as follows:
<Button x:Name="btn" Height="30" Margin="0,5,0,0">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="Green" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border x:Name="bd"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ContentPresenter VerticalAlignment="Center" HorizontalAlignment="Center" />
</Border>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding ButtonPressed, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" Value="True">
<Setter TargetName="bd" Property="Background" Value="Purple" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Button.Style>
</Button>
then I'd create a property:
private bool _buttonPressed;
public bool ButtonPressed
{
get
{
return _buttonPressed;
}
set
{
if (_buttonPressed != value)
{
_buttonPressed = value;
OnPropertyChanged(nameof(ButtonPressed));
}
}
}
and in the place that you're about to raise that event I'd simply change our boolean value state
var invokeProv = (IInvokeProvider)(new ButtonAutomationPeer(ButtonHome).GetPattern(PatternInterface.Invoke));
invokeProv?.Invoke();
ButtonHome.RaiseEvent(new RoutedEventArgs(ButtonBase.ClickEvent));
ButtonPressed = true;
The things that you have to remember about within this solution:
Implement INotifyPropertyChanged interface,
Make sure that your button template can reach the datacontext, where our property is created, for example I've setup RelativeSource to the Window ancestor type which isn't really nessecary i think. You might do it better and faster.
If there's a better solution for raising visual state of UI elements I'd like to know about it! Kind Regards.

Related

XAML ContentControl not changing visibility

I have a little problem with my code. I have a content control that I want to switch the visibility on, based on a value of a property.
I have a toggle button that changes the value of the property IsListView and the icon without a problem.
<ToggleButton Width="26" Height="26" VerticalAlignment="Center" IsChecked="{Binding IsListView}" Command="{Binding SetItemsViewStyle}" Margin="0,0,5,0">
<ToggleButton.Style>
<Style TargetType="{x:Type ToggleButton}" BasedOn="{StaticResource MahApps.Styles.Button.Circle}">
<Setter Property="Content" Value="{iconPacks:BootstrapIcons List, Width=12, Height=12}" />
<Setter Property="ToolTip" Value="Switch to list display"/>
<Style.Triggers>
<Trigger Property="IsChecked" Value="true">
<Setter Property="Content" Value="{iconPacks:Material Apps, Width=12, Height=12}" />
<Setter Property="ToolTip" Value="Switch to tile display"/>
</Trigger>
</Style.Triggers>
</Style>
</ToggleButton.Style>
</ToggleButton>
Then I have a style for the content control that I would like to hide or display depending on the value of the IsListView property:
<Style x:Key="ListViewStyle" TargetType="ContentControl">
<Setter Property="Visibility" Value="Collapsed"/>
<Setter Property="Content" Value="{x:Null}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsListView}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
<Setter Property="Content">
<Setter.Value>
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalAlignment="Stretch" VerticalScrollBarVisibility="Auto" Margin="0,31,0,0" >
<DataGrid ItemsSource="{Binding Projects}"/>
</ScrollViewer>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
The ContentControl is pretty much empty:
<ContentControl Style="{StaticResource ListViewStyle}">
</ContentControl>
The contents are displayed correctly when the screen is initialized (the IsListView is initialized to true), but the ContentControl does not dissapear when I click the toggle button. The code behind is executed, including fetching data from the data store, but the screen is not refreshed it seems to me.
What am I missing?
Ok, I found out what was the problem. I should have mentioned in my question that I am using ReactiveUI.
I had a property IsListView that was defined like this:
public bool IsListView { get; set; }
It has to be defined like this to ensure that the events are propagated to the view.
private bool _isListView;
public bool IsListView
{
get { return _isListView; }
set { this.RaiseAndSetIfChanged(ref _isListView, value); }
}
Now the controls hide and show as per my requirement.

WPF Custom/User Control for button with text and icon. Which one should I choose?

I'd like to refactor the following code using a custom control for the button.
Xaml file: LoginWindow.xaml
<Button Grid.Column="0" Style="{DynamicResource BlackLoginButton}" Click="btnLogin_Click" IsDefault="True">
<DockPanel>
<TextBlock Text="text1" VerticalAlignment="Center"/>
<Image Source="../../../Assets/Images/apply.png" Style="{StaticResource ImageButton}" />
</DockPanel>
</Button>
Code behind: LoginWindow.xaml.cs
private void btnLogin_Click(object sender, EventArgs e)
{
Login();
}
I particular I'd like to have a structure like this:
<local:CustomButton>
Grid.Column="0"
IsDefault="True"
Style="{DynamicResource BlackLoginButton}"
Click="btnLogin_Click"
Text="ciao"
Image="../../../Assets/Images/apply.png">
</local:CustomButton>
I try to use both Custom Control and User Control.
Here is my UserControl xaml
<UserControl x:Class="foo.View.CustomUserControl.IconButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Button Style="{DynamicResource BlackLoginButton}">
<DockPanel>
<TextBlock VerticalAlignment="Center" Text="{Binding ElementName=Button, Path=Text}" />
<Image Source="{Binding ElementName=Button, Path=Image}" Style="{StaticResource ImageButton}" />
</DockPanel>
</Button>
</UserControl>
And the code behind:
using System.Windows;
using System.Windows.Media;
namespace Mhira3D.View.CustomUserControl
{
public partial class IconButton
{
public static DependencyProperty ClickProperty = DependencyProperty.Register("Click", typeof(RoutedEventHandler),
typeof(IconButton));
public static DependencyProperty ImageProperty = DependencyProperty.Register("Image", typeof(ImageSource),
typeof(IconButton));
public static DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(IconButton));
public IconButton()
{
InitializeComponent();
}
public RoutedEventHandler Click
{
get { return (RoutedEventHandler) GetValue(ClickProperty); }
set { SetValue(ClickProperty, value); }
}
public ImageSource Image
{
get { return (ImageSource) GetValue(ImageProperty); }
set { SetValue(ImageProperty, value); }
}
public string Text
{
get { return this.GetValue(TextProperty) as string; }
set { this.SetValue(TextProperty, value); }
}
}
}
The main problem of this structure is that I cannot use Button property (I did not inherit from button), for example I cannot use IsDefault.
I think it could be an alternative use a CustomControl, to use the button property in a better way, like this (in thi example I put only the Image property):
public class IconButtonCustom : Button
{
static IconButtonCustom()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(IconButtonCustom), new FrameworkPropertyMetadata(typeof(IconButtonCustom)));
}
public ImageSource Image
{
get { return GetValue(SourceProperty) as ImageSource; }
set { SetValue(SourceProperty, value); }
}
public static readonly DependencyProperty SourceProperty =
DependencyProperty.Register("Image", typeof(ImageSource), typeof(IconButtonCustom));
}
And the style in Generic.xaml
<Style TargetType="{x:Type customUserControl:IconButtonCustom}" BasedOn="{StaticResource BlackLoginButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type customUserControl:IconButtonCustom}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<DockPanel>
<TextBlock VerticalAlignment="Center" Text="{Binding ElementName=Button, Path=Text}" />
<Image Source="{Binding ElementName=Button, Path=Image}" Style="{StaticResource ImageButton}" />
</DockPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Now I have some questions:
Which of these methods is the more suitable to create my custom button?
Using both methods, I have problem mapping the click function, in fact I got the System.Windows.Markup.XamlParseException because WPF cannot convert the string btnLogin_Click in the function Click.
I've never used Generic.xaml, is my example correct or should be improved in some way?
Alrighty, so most Framework elements have a handy-dandy property called Tag that exists for instances like these where we could use a way to piggy-back something into a template without having to go and declare additional dependency properties and such.
So if we take a default Button style template (Right-Click the button->Edit Template->Edit a Copy) we see a ContentPresenter in there that will generally pass any CLR object. So we're good there for your text, or whatever else you want.
We now have multiple options to accomplish your goal. One would be to simply pass in your two elements this way (in pseudo);
<Button>
<StackPanel Orientation="Horizontal">
<TextBlock/>
<Image/>
</StackPanel>
</Button>
Except that seems a bit tedious. So we move into the Style Template option and instead do something like this to it;
<Style x:Key="SpecialButtonStyle" TargetType="{x:Type Button}">
<!-- We set a default icon/image path for the instance one isn't defined. -->
<Setter Property="Tag" Value="../../../Assets/Images/apply.png"/>
<Setter Property="FocusVisualStyle" Value="{StaticResource FocusVisual}"/>
<Setter Property="Background" Value="{StaticResource Button.Static.Background}"/>
<Setter Property="BorderBrush" Value="{StaticResource Button.Static.Border}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Padding" Value="1"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="true">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ContentPresenter x:Name="contentPresenter" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
<!-- This is where we've added an Image to our Button with it's source bound to Tag. PS - In everything else like Silverlight, WP, UWP, Embedded etc, it's just {TemplateBinding Tag} -->
<Image Grid.Column="1" Source="{Binding Path=Tag, RelativeSource={RelativeSource TemplatedParent}}"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsDefaulted" Value="true">
<Setter Property="BorderBrush" TargetName="border" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Background" TargetName="border" Value="{StaticResource Button.MouseOver.Background}"/>
<Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.MouseOver.Border}"/>
</Trigger>
<Trigger Property="IsPressed" Value="true">
<Setter Property="Background" TargetName="border" Value="{StaticResource Button.Pressed.Background}"/>
<Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.Pressed.Border}"/>
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Background" TargetName="border" Value="{StaticResource Button.Disabled.Background}"/>
<Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.Disabled.Border}"/>
<Setter Property="TextElement.Foreground" TargetName="contentPresenter" Value="{StaticResource Button.Disabled.Foreground}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Notice the added Image element with it's Source set to {TemplateBinding Tag} along with a setter at the top specifying a string path to what could be a default image.
So now at the instance we could just do;
<Button Content="Blah Blah Blah" Click="Click_Handler"
Style="{StaticResource SpecialButtonStyle}"/>
...and we would get a button, with the default icon, and your click handler. Except now we need to display different icons for each instance. So we could do something like;
<Button Content="Blah Blah Blah"
Click="Click_Handler"
Tag="../../../Assets/Images/DIFFERENTIMAGE.png"
Style="{StaticResource SpecialButtonStyle}"/>
Except that still seems a little daunting. So we could take it a step further and make your asset strings into resources. Add the mscorlib namespace to you dictionary as something like `xmlns:sys" prefix and you could do this;
<sys:String x:Key="imageONE">../../../Assets/Images/imageONE.png</sys:String>
<sys:String x:Key="imageTWO">../../../Assets/Images/imageTWO.png</sys:String>
<sys:String x:Key="imageTHREE">../../../Assets/Images/imageTHREE.png</sys:String>
This does us a few benefits. One, much easier maintenance in case you need to change the file path/name for one of those icons. You can do it in just one spot and it will inherit to all instances. Instead of having to track down each instance it's hard set at. It also allows us to invoke these at the instance as a resource, so now our Button instance would look like this;
<Button Content="Blah Blah Blah"
Click="Click_Handler"
Tag="{StaticResource imageTWO}"
Style="{StaticResource SpecialButtonStyle}"/>
Voila, you're done. However another observation you should consider, is your file paths. I would definitely use Pack Uri instead. As long as your path is correct and your build action on the image is also you should be set.
Hope this helps, cheers.

how to set ContentPresenter Contents Background

I have a ListBox which is Bound to an ObesvableCollection of dynamically created UserControls.
<ListBox x:Name="myListBox">
<ListBox.Style>
<Style TargetType="{x:Type ListBox}">
<Setter Property="ItemsSource" Value="{Binding userControlsCollection}"/>
....
</Style>
</LIstBox.Style>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<EventSetter Event="Selector.Selected" Handler="ListBox_Selected"/>
<EventSetter Event="Selector.Unselected" Handler="ListBox_UnSelected"/>
<Setter Property="Background" Value="{DynamicResource DefaultBackground}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
SnapsToDevicePixels="true"
Width="{Binding ActualWidth,RelativeSource={RelativeSource FindAncestor,AncestorType=ListBoxItem,AncestorLevel=1}}"
Height="{Binding ActualHeight,RelativeSource={RelativeSource FindAncestor,AncestorType=ListBoxItem, AncestorLevel=1}}"
/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox>
I would like to set the Background of the selected control should be something like that
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="{DynamicResource SelectedBackground}"/>
</Trigger>
</Style.Triggers>
but that way I set the ListBoxItem Background and it doesn't propagate the the UserControls Background...
the way I solve it now is using the Selector.Selected/UnSelected event handlers like this
private void ListBox_Selected(object sender, RoutedEventArgs e)
{
var item = e.Source as ListBoxItem;
var ctrl= item.Content as myControl;
if (ctrl!= null)
{
ctrl.Background = new SolidColorBrush(DefaultSelectedBackground);
}
}
Any Idea would be greatly appriciated
Try to keep your ItemContainerStyle simple. If you need to mess with the Template of the item, use ItemTemplate or RelativeSource bindings to achieve what you need.
To get your requirement with RelativeSource Binding, I'd just have the ItemContainerStyle as something like:
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Background"
Value="BurlyWood" />
<Setter Property="HorizontalContentAlignment"
Value="Stretch" />
<Setter Property="VerticalContentAlignment"
Value="Stretch" />
<Style.Triggers>
<Trigger Property="IsSelected"
Value="True">
<Setter Property="Background"
Value="Tomato" />
</Trigger>
<Trigger Property="IsMouseOver"
Value="True">
<Setter Property="Background"
Value="CadetBlue" />
</Trigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
Now to get this Background in the UserControl, my UserControl xaml would be like:
<UserControl ...
Background="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ListBoxItem}},
Path=Background}">
That's it, we're sorted.
You can get the demo of this from: Here
Update:
If you're creating the UserControl from code-behind(not sure why you need to but anyways), Assign the Binding in code-behind too. Something like:
public UserControl CreateUserControl(string text) {
Binding binding = new Binding {
Path = new PropertyPath(BackgroundProperty),
RelativeSource = new RelativeSource() {
Mode = RelativeSourceMode.FindAncestor,
AncestorType = typeof(ListBoxItem)
}
};
var uc = new UserControl {
Content = new TextBlock {
Text = text,
FontSize = 24,
FontWeight = FontWeights.Bold,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center
}
};
BindingOperations.SetBinding(uc, BackgroundProperty, binding);
return uc;
}
and the ItemContainerStyle would remain the same as before and you should be done.
Demo for this approach: Here
To acheive what you are saying, you could wrap your ContentPresenter in a panel (e.g. Grid) and use TemplateBinding to set the background .
Sample Control Template:
<ControlTemplate TargetType="ListBoxItem">
<Grid Background="{TemplateBinding Background}">
<ContentPresenter Content="{Binding}" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="Fuchsia" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
Try setting the style trigger like this:
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsSelectedProperty}" Value="True">
<Setter Property="Control.Background" Value="Red" />
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsSelectedProperty}" Value="False">
<Setter Property="Control.Background" Value="Black" />
</DataTrigger>
</Style.Triggers>
Thanks

Strange button style behaviour, changing colors like storyboard

I want to style a button depending on a ViewModel property, which is not part of the button.
So I created the following
The Button XAML:
<Button Content="DisableButton"
Height="27"
HorizontalAlignment="Left"
Margin="95,197,0,0"
VerticalAlignment="Top"
Width="120"
Style="{DynamicResource ButtonOffline}"
Click="ButtonDisableClick">
</Button>
The style (the Trigger with Property does not work. also tried DataTrigger)
<Grid.Resources>
<Style x:Key="ButtonOffline" TargetType="{x:Type Button}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsOnline}" Value="false">
<Setter Property="Background" Value="#FF808080"></Setter>
<Setter Property="BorderBrush" Value="Red"></Setter>
<Setter Property="BorderThickness" Value="5"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding IsOnline}" Value="true">
<Setter Property="Background" Value="#FF990000"></Setter>
<Setter Property="BorderBrush" Value="Blue"></Setter>
<Setter Property="BorderThickness" Value="2"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Resources>
The property
private bool isOnline;
public bool IsOnline
{
get
{
return isOnline;
}
set
{
isOnline = value;
NotifyPropertyChanged("IsOnline");
}
}
And my click handler
private void ButtonDisableClick(object sender, RoutedEventArgs e)
{
var isOnline = (this.DataContext as ComboBoxSampleViewModel).IsOnline;
(this.DataContext as ComboBoxSampleViewModel).IsOnline = !isOnline;
}
Now if I click the button, something like a storyboard begins
and the buttons starts to change colors like an animation.
Whats wrong with the code?
Update
Reproduced in a small sample which just has the button features posted here.
Nothing (except the basic INotifyPropertyChanged implementation) else
which is not posted here.
You must set Focusable property to false, this will stop the button flashing.
<Button Content="DisableButton"
Focusable="False"
Height="27"
HorizontalAlignment="Left"
Margin="95,197,0,0"
VerticalAlignment="Top"
Width="120"
Style="{DynamicResource ButtonOffline}"
Click="ButtonDisableClick">
</Button>
This happens because there must be some triggers in the default template that are changing the background property.
The second solution is create your own template to button.
<Grid.Resources>
<Style x:Key="ButtonOffline" TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Grid x:Name="Grid">
<Border CornerRadius="2" x:Name="Border" Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"
Padding="{TemplateBinding Padding}"/>
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
RecognizesAccessKey="True"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding IsOnline}" Value="False">
<Setter Property="Background" Value="#FF808080"></Setter>
<Setter Property="BorderBrush" Value="Red"></Setter>
<Setter Property="BorderThickness" Value="5"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding IsOnline}" Value="True">
<Setter Property="Background" Value="#FF990000"></Setter>
<Setter Property="BorderBrush" Value="Blue"></Setter>
<Setter Property="BorderThickness" Value="2"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Resources>

How can I change the property of an Element based on the property of an INotifyPropertyChanged object in an ObservableCollection?

I've got an ObservableCollection<User> full of User objects which implement INotifyPropertyChanged. The collection is set as the DataContext of my Window, which contains a ListBox (whose ItemsSource is also set to the same collection), a number of TextBoxes, and a save Button, standard CRUD setup.
I want to change the background of the save Button (and the background of the row in the ListBox which corresponds to the "current item") if one of the properties of the User objects changes. Should I be looking at styles and triggers?
I have the following Style applied to my save Button, and the User objects have a public bool IsDirty property.
<Style x:Key="PropertyChangedStyle" TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding Source=???, Path=IsDirty}" Value="True">
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
<Button ... Style="{StaticResource PropertyChangedStyle}">
I think I'm on the right track, but I don't understand how to point the binding to "the current item in an observable list which is set as the datacontext", where "current item" in this case is described by CollectionViewSource.GetDefaultView(ListOfUsers).CurrentItem (where ListOfUsers is my ObservableCollection<User>).
The DataContext of each of the items in your ListBox will be automatically bound to your User instances, so it is not necessary to set the source in your binding. You can bind directly from the style of your ListBoxItems to properties on your User instances.
You can achieve it like this:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ASD_Answer011.MainWindow"
x:Name="Window"
Title="MainWindow"
Width="640" Height="480">
<Window.Resources>
<DataTemplate x:Key="ItemTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Property1}"/>
<CheckBox IsChecked="{Binding Property2}"/>
</StackPanel>
</DataTemplate>
<Style x:Key="ListBoxItemStyle1" TargetType="{x:Type ListBoxItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsDirty}" Value="True">
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
<Setter Property="HorizontalContentAlignment" Value="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
<Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
<Setter Property="Padding" Value="2,0,0,0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="true">
<Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="true"/>
<Condition Property="Selector.IsSelectionActive" Value="false"/>
</MultiTrigger.Conditions>
<Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
</MultiTrigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid x:Name="LayoutRoot" DataContext="{Binding Source={StaticResource SampleDataSource}}">
<ListBox ItemTemplate="{DynamicResource ItemTemplate}" ItemsSource="{Binding Collection}" ItemContainerStyle="{DynamicResource ListBoxItemStyle1}"/>
</Grid>
</Window>
This is how it looks when the application is running:
WPF supports the idea of a "current item" in a collection, and will track the current item for you. You can write a binding path that refers to a collection's current item.
See the "Current Item Pointers" section on the Data Binding Overview page on MSDN.
I'm thinking that, if your ListBox's ItemsSource is bound to (for example) {Binding ListOfUsers}, then your button could use {Binding ListOfUsers/IsDirty}.
I haven't used this much, but I think you may have to set your ListBox's IsSynchronizedWithCurrentItem property to True to make this work.

Categories

Resources