I have extended ListView class and created two DataTemplate for it in the separate Resource file. My question is how I can add event handlers for the Checkbox (and other items) in the DataTemplate?
MyListView.cs
using System.Windows;
using System.Windows.Controls;
namespace WpfCustomControlLibrary1
{
public class MyListView : ListView
{
public enum ListMode
{
List, ListCheck
}
public static readonly DependencyProperty ModeProperty = DependencyProperty.Register
(
"Mode",
typeof(ListMode),
typeof(MyListView),
new PropertyMetadata(ListMode.List)
);
public ListMode Mode
{
get { return (ListMode)GetValue(ModeProperty); }
set { SetValue(ModeProperty, value); }
}
}
}
MyListViewItem.cs
using System.Windows;
using System.Windows.Controls;
namespace WpfCustomControlLibrary1
{
public class MyListViewItem:Control
{
public static readonly DependencyProperty TextProperty = DependencyProperty.Register
(
"Text",
typeof(string),
typeof(MyListViewItem),
new UIPropertyMetadata(string.Empty)
);
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty IsCheckedProperty = DependencyProperty.Register
(
"IsChecked",
typeof(bool),
typeof(MyListViewItem),
new UIPropertyMetadata(false)
);
public bool IsChecked
{
get { return (bool)GetValue(IsCheckedProperty); }
set { SetValue(IsCheckedProperty, value); }
}
}
}
ResourceDictionary.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfCustomControlLibrary1">
<DataTemplate x:Key="ItemTemplate_List">
<TextBlock Text="{Binding Text}" HorizontalAlignment="Left"/>
</DataTemplate>
<DataTemplate x:Key="ItemTemplate_ListCheck">
<Grid>
<CheckBox IsChecked="{Binding IsChecked}"/>
<TextBlock Text="{Binding Text}" Margin="20,0,0,0" HorizontalAlignment="Left"/>
</Grid>
</DataTemplate>
<Style TargetType="{x:Type local:MyListView}">
<Style.Triggers>
<Trigger Property="Mode" Value="List">
<Setter Property="ItemTemplate" Value="{StaticResource ItemTemplate_List}"/>
</Trigger>
<Trigger Property="Mode" Value="ListCheck">
<Setter Property="ItemTemplate" Value="{StaticResource ItemTemplate_ListCheck}"/>
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
Your question refers to Event handlers and the use of them in Resource files. Access to the code-behind cannot be expressed directly in XAML stored in resource files.
While you are able to do the following in the XAML of a UserControl,
<CheckBox Click="CheckBox_OnClick"/>
this is simply not possible when the style is detached in a separate resource directory.
This is where the ICommand interface becomes useful to trigger, usually, on the ViewModel but can also be used to trigger code-behind. The syntax then becomes (if you are looking to trigger code-behind):-
<CheckBox Command="{Binding CheckBoxClickedCommand, RelativeSource={RelativeSource FindAncestor, AncestorType='MyListView'}}"/>
This would allow you to trigger code in your MyListView class if you:-
Add a property called CheckBoxClickedCommand to your MyListView class
Add the supporting interface for ICommand on that property of that class
It is common to use the RelayCommand implementation so you can supply Lambda Expressions to simplify the implementation. A quick search on RelayCommand will give you some guidance.
This all sounds good until you find out that only certain controls (e.g. Button, Checkbox, RadioButton) implement the Command pattern as standard.
Luckily since Blend 3 we now have more scope to use the ICommand pattern using Behaviours, again something you might want to research.
<CheckBox Command="{Binding CheckBoxClickedCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseEnter">
<i:InvokeCommandAction Command="{Binding CheckBoxMouseEnterCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Checkbox>
Here other events such as MouseEnter can be used to fire off an ICommand call. This also means other controls that did not support the Command= property can also call the code-behind.
Related
I have a custom user control that has a checkbox and a button with backing dependency properties.
I also have a window with an ItemsControl and I bind that control's "ItemsSource" to an ObservableCollection containing said user controls.
My goal is to be able to access the dependency properties in the window's view model, so I can check whether the checkbox of each member of the collection is checked or not and when the user clicks the button - remove the user control containing the button from the collection.
This is my code
User control XAML:
<CheckBox IsChecked="{Binding cbChecked,
Mode=OneWayToSource,
UpdateSourceTrigger=PropertyChanged,
RelativeSource={RelativeSource FindAncestor,
AncestorType=UserControl}}"/>
<Button Content="X"
Command="{Binding RemoveCommand,
RelativeSource={RelativeSource FindAncestor,
AncestorType=UserControl}}">
(note I am not sure if I need the "UpdateSourceTrigger")
User control codebehind:
public ICommand RemoveCommand
{
get { return (ICommand)GetValue(RemoveCommandProperty); }
set { SetValue(RemoveCommandProperty, value); }
}
// Using a DependencyProperty as the backing store for RemoveCommand. This enables animation, styling, binding, etc...
public static readonly DependencyProperty RemoveCommandProperty =
DependencyProperty.Register("RemoveCommand", typeof(ICommand), typeof(CustomUserControl), new PropertyMetadata(OnAnyPropertyChanged));
public bool cbChecked
{
get { return (bool)GetValue(cbCheckedProperty); }
set { SetValue(cbCheckedProperty, value); }
}
// Using a DependencyProperty as the backing store for cbChecked. This enables animation, styling, binding, etc...
public static readonly DependencyProperty cbCheckedProperty =
DependencyProperty.Register("cbChecked", typeof(bool), typeof(CustomUserControl), new PropertyMetadata(OnAnyPropertyChanged));
static void OnAnyPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
=> (obj as CustomUserControl).OnAnyPropertyChanged(args);
void OnAnyPropertyChanged(DependencyPropertyChangedEventArgs args)
=> AnyPropertyChanged?.Invoke(this, args);
Window XAML:
<ScrollViewer VerticalScrollBarVisibility="Visible" Grid.Row="0">
<ItemsControl ItemsSource="{Binding ControlsCollection}" HorizontalAlignment="Stretch" VerticalAlignment="Top">
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="FrameworkElement.Margin" Value="5,20,0,0"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</ScrollViewer>
How can I make it so when the user clicks the "X" button the user control that holds it is removed from the collection AND be able to check the checkbox "checked" status from the window's view model?
First, you should not hold an element of view in view model. The item of ObservableCollection should be item's view model rather than UserControl.
Second, you don't need to use a UserControl in such case. A DataTemplate which has the same elements will suffice.
For example, a minimal item's view model would be as follows.
using CommunityToolkit.Mvvm.ComponentModel;
public partial class ItemViewModel : ObservableObject
{
[ObservableProperty]
public bool _checked;
}
Then, window's view model.
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Windows.Input;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
public class MainWindowViewModel : ObservableObject
{
public ObservableCollection<ItemViewModel> ItemsCollection { get; } = new();
public MainWindowViewModel()
{
ItemsCollection.Add(new ItemViewModel());
ItemsCollection.Add(new ItemViewModel());
ItemsCollection.Add(new ItemViewModel());
}
public ICommand RemoveCommand => _removeCommand ??= new RelayCommand<ItemViewModel>(item => Remove(item));
private RelayCommand<ItemViewModel>? _removeCommand;
private void Remove(ItemViewModel? item)
{
if (item is not null)
{
Debug.WriteLine($"Checked:{item.Checked}");
ItemsCollection.Remove(item);
}
}
}
The window's view. The elements of DataTemplate of ItemsControl.ItemTemplate are the same as your UserControl but bindings are modified.
<Window x:Class="WpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp"
Title="MainWindow"
Width="600" Height="300">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<ScrollViewer VerticalScrollBarVisibility="Visible">
<ItemsControl ItemsSource="{Binding ItemsCollection}"
HorizontalAlignment="Stretch" VerticalAlignment="Top">
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="FrameworkElement.Margin" Value="5,20,0,0"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<CheckBox IsChecked="{Binding Checked, Mode=OneWayToSource}"/>
<Button Content="X"
Command="{Binding DataContext.RemoveCommand, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}"
CommandParameter="{Binding DataContext, RelativeSource={RelativeSource Self}}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Window>
I'm working on a WPF application according to the MVVM pattern and am facing a challenge that I abstracted in the code below.
The app contains a DataGrid with 2 ComboBox columns (each generated in a different manner). The aim is to have a ComboBox present only those items that have not yet been selected by the other ComboBoxes in the same column.
The comboboxes are Bound to an ObservableCollection of Professions. Each profession has a Boolean "Selectable", and a ComboBox should only show those entries with a value of "true".
The list contains:
Painter
Poet
Scientist
To simulate an interactive Command from XAML to the ViewModel, I placed a button that will set the Scientist to Selectable to "false".
App.xaml:
<Application x:Class="wpf_ComboBoxColumn.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
</Application>
MainWindow.xaml.cs:
using System.Windows;
namespace wpf_ComboBoxColumn
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainViewModel();
}
}
}
MainWindow.xaml:
<Window x:Class="wpf_ComboBoxColumn.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:wpf_ComboBoxColumn"
xmlns:viewModel="clr-namespace:wpf_ComboBoxColumn"
Title="Combobox Column Binding" Height="350" Width="460">
<Window.Resources>
<ResourceDictionary>
<Style TargetType="{x:Type ComboBox}" BasedOn="{StaticResource {x:Type ComboBox}}">
<Setter Property="ItemsSource"
Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" />
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="ComboBoxItem" BasedOn="{StaticResource {x:Type ComboBoxItem}}">
<Style.Triggers>
<DataTrigger Binding="{Binding Selectable}" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
<DataTrigger Binding="{Binding Selectable}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
</Window.Resources>
<Grid>
<Grid.DataContext>
<viewModel:MainViewModel />
</Grid.DataContext>
<DataGrid ItemsSource="{Binding People}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}" />
<DataGridComboBoxColumn
Header="ComboBoxColumn"
SelectedValueBinding="{Binding Description, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
SelectedValuePath="Description"
DisplayMemberPath="Description"
>
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Professions}"/>
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Professions}"/>
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
<DataGridTemplateColumn Header="TemplateColumn">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox
ItemsSource="{Binding Path=DataContext.Professions, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}"
DisplayMemberPath="Description"
SelectedValue="{Binding Profession, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<Button x:Name="button" Content="Button" HorizontalAlignment="Left" Margin="210,290,0,0" VerticalAlignment="Top" Width="75" Command="{Binding DebugCommand}"/>
</Grid>
</Window>
CustomCommand.cs (ICommand implementation):
using System;
using System.Windows.Input;
namespace wpf_ComboBoxColumn
{
public class CustomCommand: ICommand
{
private readonly Action<object> execute;
public CustomCommand(Action<object> execute)
{
this.execute = execute;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
execute(parameter);
}
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
}
}
MainViewModel.cs:
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;
namespace wpf_ComboBoxColumn
{
public class NotifyUIBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Profession
{
public string Description { get; set; }
public Boolean Selectable { get; set; }
}
public class Person
{
public string Name { get; set; }
public string Profession { get; set; }
}
public class MainViewModel : NotifyUIBase
{
public ObservableCollection<Person> People { get; set; }
public ObservableCollection<Profession> Professions { get; set; }
public ICommand DebugCommand { get; set; }
public MainViewModel()
{
DebugCommand = new CustomCommand(Debug);
People = new ObservableCollection<Person>
{
new Person{Name="Tom", Profession="" },
new Person{Name= "Dick", Profession="" },
new Person{Name= "Harry", Profession="" }
};
Professions = new ObservableCollection<Profession>
{
new Profession{ Description="Painter", Selectable=true},
new Profession{ Description="Poet", Selectable=true},
new Profession{ Description="Scientist", Selectable=true},
};
}
private void Debug(object obj)
{
Professions[2].Selectable = false;
}
}
}
Now consider the following scenario (I'm still trying to figure out how to include screen shots):
Open the app: This will show a grid with 3 columns:
First column shows the names "Tom", "Dick" and "Harry".
Second column contains a ComboBox for each person. It requires multiple clicks to open.
Third column also contains a ComboBox for each person. This one is recognizable as such.
Choose "Scientist" for Tom
Click the button (to fake that we executed code that changed Profession.Selectable)
Click on the Combobox for Dick
This will indeed show the remaining Professions (without Scientist), for the rightmost column of ComboBoxes. The leftmost column will still show all options, so this one fails right away.
Click on the Combobox for Tom again
This will, even for the rightmost column of ComboBoxes, show all options again (or rather: still)!
It turns out that the list, once shown, is not dynamically updated. Until we click on it, it is (makes me think of Quantum Mechanics, but that's another story)
The question is: Is there a way to force a refresh of the ItemsSource? Preferrably, of course, respecting MVVM, but at this point, I'll go for any working solution, using either ComboBox-type.
Thanks!
You need to raise the PropertyChanged event on the Selectable property. You're binding to it, and then you're changing it, so if you want the view to change based on this property, it needs to raise PropertyChanged.
I'm wondering if is possible bound a Command to the ComboBox, I've actually implemented the Command logic on a Menu, in this way:
<Menu HorizontalAlignment="Left" VerticalAlignment="Stretch">
<MenuItem Header="Theme" Width="100"
ItemContainerStyle="{StaticResource ThemeColorMenuItemStyle}"
ItemsSource="{Binding Themes, Mode=OneTime}" />
</Menu>
where the ItemContainerStyle have this structure:
<Style x:Key="AccentColorMenuItemStyle"
BasedOn="{StaticResource MetroMenuItem}" TargetType="{x:Type MenuItem}">
<Setter Property="CommandParameter" Value="{Binding }" />
<Setter Property="Command" Value="{Binding DataContext.ApplyAccentCommand,
RelativeSource={RelativeSource AncestorType=Window}}" />
<Setter Property="Header" Value="{Binding Name, Mode=OneWay}" />
<Setter Property="Icon" Value="{StaticResource AccentMenuIcon}" />
</Style>
and this is the command:
public ICommand ApplyAccentCommand { get; } = new SimpleCommand(o => ApplyAccent((Swatch)o));
private static void ApplyAccent(Swatch swatch)
{
new PaletteHelper().ReplaceAccentColor(swatch);
}
this MenuItem bound a Theme collection provided by MaterialDesignInXaml as Swatch model, that have this class:
public class Swatch
{
public Swatch(string name, IEnumerable<Hue> primaryHues, IEnumerable<Hue> accentHues);
public string Name { get; }
public Hue ExemplarHue { get; }
public Hue AccentExemplarHue { get; }
public IEnumerable<Hue> PrimaryHues { get; }
public IEnumerable<Hue> AccentHues { get; }
public bool IsAccented { get; }
public override string ToString();
}
so, returning to the question: is possible have this logic on a ComboBox? 'cause the MenuItem doesn't have the SelectedItem property, and I need this.
You can use Blend Behaviors and bind an event to a command. You will need to refer System.Windows.Interactivity namespace, which you can get by installing the Expression.Blend.Sdk NuGet package.
Once installed, add the following XAML namespace to your page:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
And then use it as follows:
<ComboBox>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding MyCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
The MvvmLight toolkit also offers more advanced version of InvokeCommandAction called EventToCommand that allows you to specify a EventArgsConverter as well to be able to get a specific value from the event's EventArgs instance.
I'm trying to set a property of an object when a specific MenuItem is chosen. For example, If the 'Start' MenuItem is chosen, the property should be set to TRUE. If a different MenuItem is chosen such as 'Stop', the property should be set to FALSE. I also set up a Command binding to start a separate window when one of the MenuItems is chosen. The command binding works and starts the separate window when the "Start" MenuItem is chosen. However, the property does not go to TRUE when "Start" is chosen.
This is what I have so far:
<Window x:Class="ServerWindows.ServerMenuBar"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:server="clr-namespace:ServerWindows;assembly=ServerWindows"
Title="Server Window" Height="65" Width="650" MaxWidth="650" MaxHeight="65" MinHeight="65" MinWidth="650" ResizeMode="NoResize">
<Window.Resources>
<Style x:Key="ServerStarted" TargetType="MenuItem">
<Style.Triggers>
<DataTrigger Binding="{Binding server:ServerMenuCommands.ServerStarted}" Value="true">
<Setter Property="Tag" Value="true"/>
</DataTrigger>
</Style.Triggers>
</Style>
<Style x:Key="ServerStopped" TargetType="MenuItem">
<Style.Triggers>
<DataTrigger Binding="{Binding server:ServerMenuCommands.ServerStarted}" Value="false">
<Setter Property="Tag" Value="false"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<MenuItem FontSize="14" Header="Start" Command="server:ServerMenuCommands.OpenPuttyDisplayCommand" Style="{StaticResource ServerStarted}"/>
<MenuItem FontSize="14" Header="Stop" Style="{StaticResource ServerStopped}"/>
</Grid>
</Window>
The class definition combines the data property with the command bindings in a static class as such:
public static class ServerMenuCommands
{
public static bool ServerStarted
{
get;
set;
}
public static readonly RoutedUICommand OpenPuttyDisplayCommand = new RoutedUICommand("Open Putty Display", "PUTTY_DISPLAY", typeof(ServerMenuCommands));
}
Finally, the use of the property is done in the window started by the Command Binding:
if (ServerMenuCommands.ServerStarted)
{
puttyText.AppendText("\n\rServer STARTED");
puttyText.ScrollToEnd();
}
else
{
puttyText.AppendText("\n\rServer STOPPED");
puttyText.ScrollToEnd();
}
Again. I just want to set the above property so I can emit a notification to the user the choice of MenuItem "Start" or "Stop" by updating the separate window started by the Command Binding.
Thanks in advance for any help on this.
EDIT
Well. After trying the answer below with several other tweaks to what is outlined below, I am unable to get what I need working. It seems that maybe there is a misunderstanding of what I want to accomplish. I want to set a property of a static object based on a choice of a particular menu item control. It seems that examples I see work the other way around. Set a property of a control based on the value of a Property (field) of an object. That is why I did not create an event in my static class. I don't want to send a PropertyChanged event anywhere. I want to set the field to a value (True or False).
Thanks for the potential solution to my problem. It looks like I'll just assign Click callbacks to the Menu Items and set the object Property that way.
Edit:Edit
I am finally able to get this working based on the edited answer below. The key changes for me was to add the Command Bindings in the XAML and call the command method that changes the static property. Also key was the static event and tying it to the static property setter and then tying it to the Trigger in the XAML. It's all good all around!
EDIT:
I have been unable to make two-way(or at least proper one-way) binding to the static property on the static class. Seems, that it is impossible.
The only way I can propose is to make the class non-static (to allow its instantiation) and bind to its static properties using the resource instance:
ServerMenuCommands and code-behind:
public class ServerMenuCommands
{
public static event EventHandler ServerStartedChanged;
private static Boolean _ServerStarted;
public static Boolean ServerStarted
{
get
{
return _ServerStarted;
}
set
{
if (value != _ServerStarted)
{
_ServerStarted = value;
if (ServerStartedChanged != null)
ServerStartedChanged(typeof(ServerMenuCommands), EventArgs.Empty);
}
}
}
public static readonly RoutedUICommand OpenPuttyDisplayCommand = new RoutedUICommand("Open Putty Display", "PUTTY_DISPLAY", typeof(ServerMenuCommands));
}
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
{
ServerMenuCommands.ServerStarted = !ServerMenuCommands.ServerStarted;
}
}
XAML :
<Window.CommandBindings>
<CommandBinding Command="server:ServerMenuCommands.OpenPuttyDisplayCommand" Executed="CommandBinding_Executed"/>
</Window.CommandBindings>
<Window.Resources>
<server:ServerMenuCommands x:Key="serverResource"/>
<Style x:Key="ServerStarted" TargetType="MenuItem">
<Style.Triggers>
<DataTrigger Binding="{Binding Source={StaticResource serverResource}, Path=ServerStarted}" Value="true">
<Setter Property="Background" Value="Green"/>
</DataTrigger>
</Style.Triggers>
</Style>
<Style x:Key="ServerStopped" TargetType="MenuItem">
<Style.Triggers>
<DataTrigger Binding="{Binding Source={StaticResource serverResource}, Path=ServerStarted}" Value="false">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<MenuItem Header="Start" Command="server:ServerMenuCommands.OpenPuttyDisplayCommand" Style="{StaticResource ServerStarted}"/>
<MenuItem Grid.Column="1" Header="Stop" Style="{StaticResource ServerStopped}"/>
<TextBox Grid.Column="2">
<TextBox.Text>
<Binding Source="{StaticResource serverResource}" Path="ServerStarted"/>
</TextBox.Text>
</TextBox>
</Grid>
This works and changes the values(backgrounds) with each OpenPuttyDisplayCommand execution.
I have created an UserControl to implement a simple ImageButton as following:
<UserControl x:Class="MyApp.Common.Controls.ImageButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Name="UC">
<Grid>
<Button Command="{Binding ElementName=UC, Path=Command}" CommandParameter="{Binding ElementName=UC, Path=CommandParameter}">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding ElementName=UC, Path=Image}"
Width="{Binding ElementName=UC, Path=ImageWidth}"
Height="{Binding ElementName=UC, Path=ImageHeight}"/>
<TextBlock Text="{Binding ElementName=UC, Path=Text}" />
</StackPanel>
</Button>
</Grid>
Here is code-behind of my control:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
namespace MyApp.Common.Controls
{
public partial class ImageButton : UserControl
{
public ImageButton()
{
InitializeComponent();
}
public ImageSource Image
{
get { return (ImageSource)GetValue(ImageProperty); }
set { SetValue(ImageProperty, value); }
}
public static readonly DependencyProperty ImageProperty =
DependencyProperty.Register("Image", typeof(ImageSource), typeof(ImageButton), new UIPropertyMetadata(null));
public double ImageWidth
{
get { return (double)GetValue(ImageWidthProperty); }
set { SetValue(ImageWidthProperty, value); }
}
public static readonly DependencyProperty ImageWidthProperty =
DependencyProperty.Register("ImageWidth", typeof(double), typeof(ImageButton), new UIPropertyMetadata(16d));
public double ImageHeight
{
get { return (double)GetValue(ImageHeightProperty); }
set { SetValue(ImageHeightProperty, value); }
}
public static readonly DependencyProperty ImageHeightProperty =
DependencyProperty.Register("ImageHeight", typeof(double), typeof(ImageButton), new UIPropertyMetadata(16d));
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(ImageButton), new UIPropertyMetadata(""));
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand), typeof(ImageButton));
public object CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.Register("CommandParameter", typeof(object), typeof(ImageButton));
}
}
The usage is simple:
<cc:ImageButton Command="{Binding SearchCommand}"
Image="/MyApp;component/Images/magnifier.png"
ImageWidth="16" ImageHeight="16"
/>
When the button (bound to DelegateCommand in my ViewModel) get disabled the image is disappear. Otherwise all works as expected.
What could be a problem?
How to make the image show in gray-scale when disabled?
UPDATE: here is the image of this strange behavior:
I copied the code that you had provided to see if I could reproduce the problem you were having. Unfortunately, when the command was disabled (or it's CanExecute was returning false) the image I used did not disappear. Could you please provide more code from your ViewModel that you think may be relevant?
To answer the second part of your question:
How to make the image show in gray-scale when disabled?
As far as I know there is no easy way to desaturate an image in WPF. Instead, I would go with the approach that #Vova had suggested which is to lower the Opacity property of the Image. You can modify your UserControl XAML you provided like so:
<UserControl x:Class="MyApp.Common.Controls.ImageButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Name="UC">
<Grid>
<Button Command="{Binding ElementName=UC, Path=Command}" CommandParameter="{Binding ElementName=UC, Path=CommandParameter}">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding ElementName=UC, Path=Image}"
Width="{Binding ElementName=UC, Path=ImageWidth}"
Height="{Binding ElementName=UC, Path=ImageHeight}">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsEnabled, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Button}} }" Value="false" >
<Setter Property="Opacity" Value="0.3" />
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<TextBlock Text="{Binding ElementName=UC, Path=Text}" />
</StackPanel>
</Button>
</Grid>
</UserControl>
In the code above I added a DataTrigger to the Image's Style property to modify the opacity of the image if the IsEnabled property of the button is equal to false.
Hope this helps!
As mentioned before, there is no built-in desaturation support in WPF, but you can easily use a shader effect to implement that. The link takes you to an implementation that lets you use the effect in a XAML-only way.
Please note that for shaders written in HLSL need to be compiled and for compilation you will need the Direct X SDK installed on your machine. Quite a beast for such a small task, admitted.
I would make it custom control,make a trigger for it at Property =isEnabled value=false. Over the image,I would add a grid with some opacity,and control that opacity or visibility with that trigger. Good luck. I don't think there is an easier way.