ListBoxItem MouseBinding in ControlTemplate InputBinding not Working MVVM - wpf

I have what I hope is an obvious problem to someone. I want an Edit command to fire when a user double clicks a ListBoxItem inside a ListBox. I have done this before in user controls but want to do it directly in a VIEW since it is a simple enough ListBox. But it will not wire up.
Here is the list box:
<ListBox SelectedItem="{Binding DataQuerySortSelected}"
ItemContainerStyle="{StaticResource SortListItemStyle}"
ItemsSource="{Binding DataQueryHolder.DataQuerySorts}">
<ListBox.InputBindings>
<KeyBinding Key="Delete" Command="{Binding DataQuerySortDelete}" />
</ListBox.InputBindings>
<ListBox.Resources>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="AllowDrop" Value="True" />
<EventSetter Event="PreviewMouseMove" Handler="DragDropListBoxItem_PreviewMouseMoveEvent" />
<EventSetter Event="Drop" Handler="DragDropListBoxItem_Drop" />
</Style>
</ListBox.Resources>
</ListBox>
Note the Delete Key binding at the top level works just fine. Here is the referenced style (brought in as a separate ResourceDictionary but putting the style inline made no difference):
<Style x:Key="SortListItemStyle" TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border Name="MainBorder">
<ContentPresenter>
<ContentPresenter.InputBindings>
<MouseBinding Gesture="LeftDoubleClick" Command="{Binding DataQuerySortEdit}" />
</ContentPresenter.InputBindings>
</ContentPresenter>
<Border.InputBindings>
<MouseBinding Gesture="LeftDoubleClick" Command="{Binding DataQuerySortEdit}" />
</Border.InputBindings>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="MainBorder" Value="Yellow" Property="Background" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I put the mouse binding in two places just to see if it made a difference but it does not. There just isnt any wiring going on there. Everything else works as expected. If I create a plain button in the View and point it at the DataQuerySortEdit Command it works as expected.
Am I missing something? Thanks for any help.
EDIT: Just adding some more info in response to J's response. I gave the Border of the ControlTemplate a binding relative to the nearest listbox and gave the listbox a name so the output would confirm it would find it. This was the output window:
System.Windows.Data Error: 40 : BindingExpression path error: 'DataQuerySortEdit' property not found on 'object' ''DataQuerySort' (HashCode=7641038)'. BindingExpression:Path=DataQuerySortEdit; DataItem='DataQuerySort' (HashCode=7641038); target element is 'MouseBinding' (HashCode=65119131); target property is 'Command' (type 'ICommand')
System.Windows.Data Error: 40 : BindingExpression path error: 'DataQuerySortEdit' property not found on 'object' ''DataQuerySort' (HashCode=50439840)'. BindingExpression:Path=DataQuerySortEdit; DataItem='DataQuerySort' (HashCode=50439840); target element is 'MouseBinding' (HashCode=3649016); target property is 'Command' (type 'ICommand')
System.Windows.Data Error: 40 : BindingExpression path error: 'DataQuerySortEdit' property not found on 'object' ''DataQuerySort' (HashCode=65588106)'. BindingExpression:Path=DataQuerySortEdit; DataItem='DataQuerySort' (HashCode=65588106); target element is 'MouseBinding' (HashCode=35717517); target property is 'Command' (type 'ICommand')
System.Windows.Data Error: 40 : BindingExpression path error: 'DataQuerySortEdit' property not found on 'object' ''DataQuerySort' (HashCode=32836053)'. BindingExpression:Path=DataQuerySortEdit; DataItem='DataQuerySort' (HashCode=32836053); target element is 'MouseBinding' (HashCode=66172851); target property is 'Command' (type 'ICommand')
System.Windows.Data Error: 40 : BindingExpression path error: 'DataQuerySortEdit' property not found on 'object' ''ListBox' (Name='SortListBox')'. BindingExpression:Path=DataQuerySortEdit; DataItem='ListBox' (Name='SortListBox'); target element is 'MouseBinding' (HashCode=28263486); target property is 'Command' (type 'ICommand')
System.Windows.Data Error: 40 : BindingExpression path error: 'DataQuerySortEdit' property not found on 'object' ''ListBox' (Name='SortListBox')'. BindingExpression:Path=DataQuerySortEdit; DataItem='ListBox' (Name='SortListBox'); target element is 'MouseBinding' (HashCode=27134857); target property is 'Command' (type 'ICommand')
System.Windows.Data Error: 40 : BindingExpression path error: 'DataQuerySortEdit' property not found on 'object' ''ListBox' (Name='SortListBox')'. BindingExpression:Path=DataQuerySortEdit; DataItem='ListBox' (Name='SortListBox'); target element is 'MouseBinding' (HashCode=7437765); target property is 'Command' (type 'ICommand')
System.Windows.Data Error: 40 : BindingExpression path error: 'DataQuerySortEdit' property not found on 'object' ''ListBox' (Name='SortListBox')'. BindingExpression:Path=DataQuerySortEdit; DataItem='ListBox' (Name='SortListBox'); target element is 'MouseBinding' (HashCode=58400697); target property is 'Command' (type 'ICommand')
So the second attempt to bind (I am guessing the one in the Border InputBinding) does fine the proper listbox but still cannot find the ICommand. I tried doing a relative find to the window, to the Gird containing the list, etc. and still cant get it to wire. I also tried as J mentions to put the relative search directly in the MouseBindings and they result in the same errors.
EDIT2: Here are the Command and properties in the ViewModel, using MVVMLight
public DataQuerySort DataQuerySortSelected
{
get { return _DataQuerySortSelected; }
set { NotifySetProperty(ref _DataQuerySortSelected, value, () => DataQuerySortSelected); }
}
private DataQuerySort _DataQuerySortSelected;
public RelayCommand DataQuerySortEdit
{
get { return new RelayCommand(_DataQuerySortEdit, CanDataQuerySortEdit); }
}
private void _DataQuerySortEdit()
{
DataQuerySortHolder = DataQuerySortSelected.Copy();
DataQuerySortEditMode = EditMode.Edit;
}
private bool CanDataQuerySortEdit()
{
return DataQuerySortSelected != null;
}
Taking out the CanDataQuerySortEdit makes not difference. I know everything works because if I create a button and point at it it works. If I also create an inputbinding in the ListBox for the mouse like the Delete key that works - as long as I click outside the ListBoxItems of course.
EDIT3: Here is Part of the View itself including the class, datacontext, and resources. I have tried doing relative bindings to be "{x:Type Window}" and "{x:Type l:ToolkitWindowBase}". The ToolkitWindowBase extends Window directly. The frmDataBrowserViewModel extends a class called ToolkitViewModelBase which extends ViewModelBase from MVVMLight:
<l:ToolkitWindowBase x:Class="GISToolkit.frmDataBrowser" x:Name="mainWindow" Icon="Images/favicon.ico"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" mc:Ignorable="d"
xmlns:l="clr-namespace:GISToolkit;assembly="
xmlns:lc="clr-namespace:GISToolkit.Controls;assembly="
xmlns:ls="clr-namespace:GISToolkit.Settings;assembly="
xmlns:system="clr-namespace:System;assembly=mscorlib"
xmlns:xctk="clr-namespace:Xceed.Wpf.Toolkit;assembly=Xceed.Wpf.Toolkit"
xmlns:xctkp="clr-namespace:Xceed.Wpf.Toolkit.Primitives;assembly=Xceed.Wpf.Toolkit"
Title="Solutions GIS Toolkit - Setup"
ResizeMode="CanResizeWithGrip" Foreground="White"
l:ControlBox.HasMaximizeButton="False" l:ControlBox.HasMinimizeButton="False" l:ControlBox.HasCloseButton="False"
Height="{Binding RunTimeHeight, Mode=TwoWay}"
Width="{Binding RunTimeWidth, Mode=TwoWay}" IsSettingsDirty="{Binding IsCurrentSettingsDirty}" IsEnabled="True">
<Window.DataContext>
<l:frmDataBrowserViewModel />
</Window.DataContext>
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Themes/DataBrowser.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
..................
<l:ToolkitWindowBase />
EDIT4: Just in case someone out there is still listing, do me a favor, create a new WPF project called "WpfMvvmApplication1" with a single Window called "BindingTestWindow" and a viewmodel called "MainWindowViewModel" Then for the window put (should be simple cut/paste unless you used different names for the files/project):
<Window x:Class="WpfMvvmApplication1.BindingTestWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:WpfMvvmApplication1"
Title="BindingTestWindow" Height="300" Width="300">
<Window.DataContext>
<l:BindingTestViewModel />
</Window.DataContext>
<Grid>
<GroupBox Header="Sorting:" >
<Grid>
<ListBox Background="White" Name="SortListBox" ItemsSource="{Binding TestCollection}">
<ListBox.InputBindings>
<KeyBinding Key="Delete" Command="{Binding TestCommand}" />
</ListBox.InputBindings>
<ListBox.Resources>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="AllowDrop" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border Name="MainBorder" Padding="0" Margin="0">
<ContentPresenter />
<Border.InputBindings>
<MouseBinding Gesture="LeftDoubleClick" Command="{Binding TestCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
</Border.InputBindings>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="MainBorder" Value="Yellow" Property="Background" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.Resources>
</ListBox>
</Grid>
</GroupBox>
</Grid>
</Window>
and for the VIEWMODEL:
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq.Expressions;
using System.Windows.Input;
namespace WpfMvvmApplication1
{
public class BindingTestViewModel : NotificationObject
{
public BindingTestViewModel()
{
TestCollection = new ObservableCollection<string>();
for (int i = 0; i < 10; i++ )
TestCollection.Add("test" + i);
}
public ICommand TestCommand { get { return new DelegateCommand(_TestCommand); } }
private void _TestCommand() { System.Diagnostics.Debugger.Break(); }
public ObservableCollection<string> TestCollection
{
get { return _TestCollection; }
set
{
_TestCollection = value;
RaisePropertyChanged(() => TestCollection);
}
}
private ObservableCollection<string> _TestCollection;
}
public class DelegateCommand : ICommand
{
private readonly Action _command;
private readonly Func<bool> _canExecute;
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public DelegateCommand(Action command, Func<bool> canExecute = null)
{
if (command == null)
throw new ArgumentNullException();
_canExecute = canExecute;
_command = command;
}
public void Execute(object parameter)
{
_command();
}
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute();
}
}
public class NotificationObject : INotifyPropertyChanged
{
protected void RaisePropertyChanged<T>(Expression<Func<T>> action)
{
var propertyName = GetPropertyName(action);
RaisePropertyChanged(propertyName);
}
private static string GetPropertyName<T>(Expression<Func<T>> action)
{
var expression = (MemberExpression)action.Body;
var propertyName = expression.Member.Name;
return propertyName;
}
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
This has nothing else in it. It gives me the binding error when the inputbinding is inside the listboxtiem but not when in, say, the listbox itself. Seems like it should work since the output says it does find the window in the FindAncestor.

Try changing your command binding to the following code. Listbox item is a visual child of listbox and you need to get your datacontext wired up correctly. I think. Note the Window Type. Change that to whatever your top level datacontext is where the command is declared and the listbox exists. Ie: usercontrol, window, etc...
<MouseBinding Gesture="LeftDoubleClick" Command="{Binding DataQuerySortEdit, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}}" />
You might be able to debug this my looking at your output window when you run your app, it will show you binding errors like this.
EDIT 1: Mvvm Light Event to Command
Okay, I totally overlooked the fact that you are suing Mvvm Light. MVVM-Light has a function built in for binding events to commands on your view model.
Add the following to your windows namespaces:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:cmd="http://www.galasoft.ch/mvvmlight"
Now change your border inside your listbox item to include the following:
<Border ...>
<i:Interaction.Triggers>
<i:EventTrigger EventName="LeftDoubleClick">
<cmd:EventToCommand Command="{Binding DataContext.DataQuerySortEdit, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Border>
Mvvm light has built this in order to facilitate exactly what you are looking for. It lets you take any event and bind a command to it. I use this for form validation through the lostfocus events of controls etc.
Make sure you have the following references in your project:
GalaSoft.MvvmLight.WPF45
GalaSoft.MvvmLight.Extras.WPF45
Microsoft.Expression.Interactions
Hope this helps
EDIT 3:
Just a last ditch effort, what if you use the following:
It adds the Datacontext to the command name.

Big thanks to J King for hanging in there. But it seems that doing in XAML does not work. I ended up doing this in the code behind of the VIEW (hope it can help someone):
public BindingTestWindow()
{
InitializeComponent();
SortListBox.ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged;
}
private void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)
{
if (SortListBox.ItemContainerGenerator.Status == System.Windows.Controls.Primitives.GeneratorStatus.ContainersGenerated)
{
BindingTestViewModel vm = (BindingTestViewModel)this.DataContext;
for(int i = 0; i < SortListBox.Items.Count; i++)
{
ListBoxItem lbi = (ListBoxItem)SortListBox.ItemContainerGenerator.ContainerFromIndex(i);
lbi.InputBindings.Clear();
lbi.InputBindings.Add(new InputBinding(vm.TestCommand, new MouseGesture(MouseAction.LeftDoubleClick)));
}
}
}
In order to manipulate the actual listboxitems (and not their content through ListBox.Items) we have to use the ItemContainerGenerator. But in order to do that, you have to wait for it to be completely generated hence the need for the event handler. Its not pretty but it works.
Ernie

I spent quite a bit of time fiddling with this problem. Managed to mix some answers together with a result that's working.
Inputbindings on a ListBox have a strange behavior. Some work on the ListBox itself, like a MiddleClick, others need to be implemented on the items.
J Kings Binding to the DataContext did the trick in the ContentPresenter of the LisBoxItemStyle.
<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}">
<ContentPresenter.InputBindings>
<MouseBinding Gesture="Ctrl+MiddleClick"
Command="{Binding DataContext.MiddleClickCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"/>
<MouseBinding Gesture="Ctrl+RightClick"
Command="{Binding DataContext.CtrlRightClickCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"/>
<MouseBinding MouseAction="RightClick"
Command="{Binding DataContext.RightClickCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"/>
</ContentPresenter.InputBindings>
</ContentPresenter>
</Border>
<ControlTemplate.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="True"/>
</MultiTrigger.Conditions>
<Setter Property="Background" TargetName="Bd" Value="{StaticResource Item.MouseOver.Background}"/>
<Setter Property="BorderBrush" TargetName="Bd" Value="{StaticResource Item.MouseOver.Border}"/>
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="Selector.IsSelectionActive" Value="False"/>
<Condition Property="IsSelected" Value="True"/>
</MultiTrigger.Conditions>
<Setter Property="Background" TargetName="Bd" Value="{StaticResource Item.SelectedInactive.Background}"/>
<Setter Property="BorderBrush" TargetName="Bd" Value="{StaticResource Item.SelectedInactive.Border}"/>
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="Selector.IsSelectionActive" Value="True"/>
<Condition Property="IsSelected" Value="True"/>
</MultiTrigger.Conditions>
<Setter Property="Background" TargetName="Bd" Value="{StaticResource Item.SelectedActive.Background}"/>
<Setter Property="BorderBrush" TargetName="Bd" Value="{StaticResource Item.SelectedActive.Border}"/>
</MultiTrigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="TextElement.Foreground" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>

Related

How to dynamically set style of an element in a User Control

In a WPF project, I have a user control (Valve.xaml) that defines a Polygon shape.
<Grid>
<Polygon Name="pValve" Points="0,50 0,20 50,50 50,20" Style="{StaticResource Valve_Open}"/>
</Grid>
I am displaying the Valve user control in a window xaml (FFG.xaml) file, like such:
<Window
<!-- removed other namespaces for brevity -->
xmlns:cl="clr-namespace:FFG.Controls;assembly=PID.Controls">
<Grid>
<cl:Valve x:Name="valve201A"></cl:Valve>
</Grid>
</Window>
I am setting the DataContext of FFG.xaml to class FFG_ViewModel.cs, and it contains an instance of the Valve_Model class. Valve_Model essentially represents the valve that is drawn on the window in FFG.xaml.
public class FFG_ViewModel : ViewModelBase {
public Valve_Model Valve201A { get; set; }
// There are other properties and methods, but I've removed them for brevity also
}
Here is the Valve_Model class:
public class Valve_Model INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
private bool _isValveOpen { get; set; }
public bool IsValveOpen {
get {
return _isValveOpen;
}
set {
_isValveOpen = value;
OnPropertyChanged("IsValveOpen");
}
}
#region INotifyPropertyChanged
protected virtual void OnPropertyChanged(string propertyName) {
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null) {
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
#endregion
}
QUESTION:
What I want is for the Style property in the Valve.xaml to change when the IsValveOpen property changes.
So if the valve is open then it would be:
<Polygon Name="pValve" Points="0,50 0,20 50,50 50,20" Style="{StaticResource Valve_Open}"/>
and when the property is changed to false then I need the style of the polygon to be changed to:
<Polygon Name="pValve" Points="0,50 0,20 50,50 50,20" Style="{StaticResource Valve_Closed}"/>
How do I go about implementing this exactly?
You could use an IMultiValueConverter.
First, let's simplify the use case. Basicly you want to swap Styles based on a given state object, which I'll represent by a ToggleButton. The fact that you're wrapping everything in a UserControl also has no influence on the underlying concept.
Demo:
Starting a fresh project
Declaring our Resources
Feeding the Window and the state to the Converter.
MainWindow.xaml
<Window
...
>
<Window.Resources>
<local:ToStyleConverter x:Key="ToStyleConverter"/>
<Style x:Key="Valve_Open" TargetType="{x:Type Polygon}">
<Setter Property="Fill" Value="Red"/>
</Style>
<Style x:Key="Valve_Closed" TargetType="{x:Type Polygon}">
<Setter Property="Fill" Value="Green"/>
</Style>
</Window.Resources>
<DockPanel>
<ToggleButton x:Name="butt" DockPanel.Dock="Bottom">Switch</ToggleButton>
<Polygon Name="pValve" Points="0,50 0,20 50,50 50,20" Stretch="Uniform">
<Polygon.Style>
<MultiBinding Converter="{StaticResource ToStyleConverter}">
<Binding RelativeSource="{RelativeSource FindAncestor,
AncestorType={x:Type Window}}"/>
<Binding ElementName="butt" Path="IsChecked"/>
</MultiBinding>
</Polygon.Style>
</Polygon>
</DockPanel>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
public class ToStyleConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
if (values[0] is Window)
{
Window win = (Window)values[0];
if ((bool)values[1])
return win.FindResource("Valve_Open");
if (!(bool)values[1])
return win.FindResource("Valve_Closed");
}
return DependencyProperty.UnsetValue;
}
public object[] ConvertBack(object values, Type[] targetType,
object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Changing to any specific use case means:
Pointing the relativesource binding to the Control that holds the Resources (Styles)
Using the second binding to add the state to the Converter (DP/INPC)
Implementing Converter logic
You can (should as much as I know) use a DataTrigger, within a ControlTemplate. Assuming that these two are your Styles:
<Window.Resources>
<Style TargetType="Polygon" x:Key="Valve_Open">
<Setter Property="Fill" Value="Red"/>
</Style>
<Style TargetType="Polygon" x:Key="Valve_Close">
<Setter Property="Fill" Value="Green"/>
</Style>
</Window.Resources>
You should add this style to the resources:
<Style x:Key="changeStyle" TargetType="Control">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Control">
<Grid>
<Polygon Name="pValve" Points="0,50 0,20 50,50 50,20" Style="{StaticResource Valve_Open}" />
</Grid>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding Valve201A.IsValveOpen}" Value="true">
<Setter TargetName="pValve" Property="Style" Value="{StaticResource Valve_Close}" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
and use them in your views:
<Control DataContext="{Binding}" Style="{StaticResource changeStyle}" />
Instead of setting the actual Style property to a new value, you could add a DataTrigger to the Style itself that changes the properties of the Polygon based on the value of the IsValveOpen source property.
Valve.xaml:
<Grid>
<Polygon Name="pValve" Points="0,50 0,20 50,50 50,20">
<Polygon.Style>
<Style TargetType="Polygon">
<!-- Copy the setters from the Valve_Closed style here -->
<Setter Property="Fill" Value="Red" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsValveOpen}" Value="True">
<!-- Copy the setters from the Valve_Open style here -->
<Setter Property="Fill" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</Polygon.Style>
</Polygon>
</Grid>
FFG.xaml:
<Grid>
<cl:Valve x:Name="valve201A" DataContext="{Binding Valve201A}" />
</Grid>

Bind Control's Property to Variable, Bind Variable to Another Control

Its hard to understand something from the Title, but its also hard for me to explain but I will try.
I have custom control to pick color.
The custom control has Property called SelectedColor:
/// <summary>
/// SelectedColor property backing ReadOnly DependencyProperty.
/// </summary>
private static readonly DependencyPropertyKey SelectedColorPropertyKey
= DependencyProperty.RegisterReadOnly("SelectedColor", typeof(Color), typeof(ImageColorPicker)
, new FrameworkPropertyMetadata(Colors.Transparent
, FrameworkPropertyMetadataOptions.AffectsRender));
/// <summary>
/// Gets or sets the color selected.
/// </summary>
/// <value>The color selected.</value>
public Color SelectedColor
{
get { return (Color)GetValue(SelectedColorPropertyKey.DependencyProperty); }
}
I want to bind that Property to a variable I have in my ViewModel which is called TabColor:
public event PropertyChangedEventHandler PropertyChanged; //Event to notify when Property changed.
/// <summary>
/// Notify that Property has Changed.
/// </summary>
/// <param name="propertyName">The name of the Property</param>
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private Brush _tabColor; //Color of the Tab.
//Return/Set the color of the Tab.
public Brush TabColor
{
get
{
return _tabColor;
}
set
{
_tabColor = value;
NotifyPropertyChanged("TabColor");
}
}
Now, I want to bind the TabColor to Properties on my Style:
<Style TargetType="{x:Type MetroStyle:MetroTabItem}">
<Setter Property="OverridesDefaultStyle" Value="True" />
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
<Setter Property="Header" Value="{Binding TabTitle}" />
<Setter Property="Foreground" Value="{Binding TabColor}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type MetroStyle:MetroTabItem}">
<Grid Height="26" Background="{TemplateBinding Background}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ContentPresenter Margin="5,0" HorizontalAlignment="Left" VerticalAlignment="Center" ContentSource="Header">
<ContentPresenter.Resources>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="{Binding Foreground, RelativeSource={RelativeSource AncestorType={x:Type MetroStyle:MetroTabItem}}}" />
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
</ContentPresenter.Resources>
</ContentPresenter>
<StackPanel Grid.Column="1" Height="16" Margin="0,0,1,0" HorizontalAlignment="Center" VerticalAlignment="Center" Orientation="Horizontal">
<Button Width="16" Click="ShowHide_Click" Content="🌕" Style="{StaticResource CustomizedMetroTabItemButton}" ToolTip="Hide" Visibility="{Binding WindowsVisible, Converter={StaticResource BooleanToVisibilityConverter}}" />
<Button Width="16" Click="ShowHide_Click" Content="🌑" Style="{StaticResource CustomizedMetroTabItemButton}" ToolTip="Show" Visibility="{Binding WindowsVisible, Converter={StaticResource InverseBooleanToVisibilityConverter}}" />
<Button Width="16" Click="Clear_Click" Content="" Style="{StaticResource CustomizedMetroTabItemButton}" ToolTip="Clear" />
<ToggleButton Width="16" x:Name="Edit" Content="î„„" Style="{StaticResource CustomizedMetroTabItemToggleButton}" ToolTip="Edit" />
<Popup IsOpen="{Binding IsChecked, Mode=TwoWay, ElementName=Edit}" StaysOpen="False" PlacementTarget="{Binding ElementName=Edit}" Placement="Left" VerticalOffset="{Binding ActualHeight, ElementName=Edit}" HorizontalOffset="{Binding Width, ElementName=Edit}" PopupAnimation="Slide">
<StackPanel>
<local:ImageColorPicker x:Name="ColorPicker" Source="Images/ColorWheel.png" HorizontalAlignment="Center" Width="100" Height="100"/>
</StackPanel>
</Popup>
</StackPanel>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="False">
<Setter Property="Background" Value="#EFEFF2" />
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#FFFFFF" />
<Setter Property="Foreground" Value="{Binding TabColor}" />
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="{Binding TabColor}" />
<Setter Property="Foreground" Value="#FFFFFF" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The reason why I use the variable as a "connector" is because I cant bind Properties out of the Tree - right? (I mean I cant do this: <Setter Property="Foreground" Value="{Binding ElementName=ColorPicker, Path=SelectedColor, Converter={StaticResource ColorToBrushConverter}}" />
I also have an Image that should simplify what I am asking:
If you have another solution as long as I will achieve what I need - I will accept.
Thanks!
EDIT:
I have a converter which converts from Color to SolidColorBrush but still, I dont know how to do the binding I want:
[ValueConversion(typeof(Color), typeof(Brush))]
public class ColorToBrushConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Color color = (Color)value;
return new SolidColorBrush(color);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
EDIT2:
Sample code with everything above
EDIT3:
A new picture (maybe it will be more understandable)
I believe all you need to do is set up a binding on Selected Color, something like:
<local:ImageColorPicker x:Name="ColorPicker" Source="Images/ColorWheel.png" HorizontalAlignment="Center" Width="100" Height="100" SelectedColor="{Binding Path=TabColor, Converter={StaticResource ColorToBrushConverter}}/>
You can always set a binding on a dependency property (but nothing else). Hopefully that clears up "Binding properties out of the tree". If not, please clarify what you don't understand and I would be happy to try and address it.
Also, the dependency property isn't set up how I am used to seeing it, so if you are still running into trouble, you might look into setting it up with the dpprop code snippet, or follow the example on MSDN.
Comments
Bindings have multiple modes, and if you specifically want just one you just set the mode variable. In your case, you would change the binding to:
SelectedColor="{Binding Path=TabColor, Converter={StaticResource ColorToBrushConverter, Mode=OneWay}}
That way, any changes to the "TabColor" property won't propagate to the ColorPicker, but changes from the ColorPicker will set the "TabColor" property. For reference the other modes are: TwoWay (the default), OneTime (get the default just on initialization), and OneWayToSource(like OneWay, but it only changes the source, and never picks up updates).
When I mentioned the "unusual" dependency property, I was referring to the style of the "SelectedColor" DP. Of course TabColor will not be a DP :).
Finally, I believe that you can bind the Value property as described. Have you tried it? The only restriction on binding is that it must be done on a DP.
Update
I could not get the ColorPicker to update the view model. Everything I've read indicates that it should be able to, but I wasn't able to figure it out. If that is an important requirement, I would suggest starting from scratch and get just that working, then keep going.
That being said, I did get the color to update. Try changing your XAML to:
<TabItem Header="Test" Foreground="{Binding ElementName=ColorPicker, Path=SelectedColor, Converter={StaticResource ColorToBrushConverter}, Mode=OneWay}">
<Grid>
<local:ImageColorPicker x:Name="ColorPicker" Source="ColorWheel.png" HorizontalAlignment="Center"/>
</Grid>
</TabItem>
For some reason, I had to rebuild a couple times to make this work. Again, I'm sorry I couldn't get the original request working.
Update 2
Apparently all you need to do is add "BindsTwoWayByDefault" to the metadata options. This will cause the "ConvertBack" function to be called since you are updating the ViewModel.
You could probably get away with having "OneWay" bindings on all the controls EXCEPT the ImageColorPicker so that they don't accidentally set the color, but in general TwoWay should be fine.

Resource 1 works, 2 doesn't

<ToggleButton Command="{Binding Path=Command}" Content="{Binding Path=DisplayName}" Template="{Utilities:BindableResource {Binding Path=TemplateResource}}">
<ToggleButton.Style>
<Style TargetType="ToggleButton">
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Template" Value="{Utilities:BindableResource {Binding Path=SelectedTemplateResource}}" />
</Trigger>
</Style.Triggers>
</Style>
</ToggleButton.Style>
</ToggleButton>
The first bindable resource works for "TemplateResource" on the template property of togglebutton however "SelectedTemplateResource" does not work within the tiggers setter. This code is within a resourcedictionary where the actual resource is within a themed resourcedictionary.
I get an error saying key is null for xamlparseexception for the setter value. I've been stairing at this for hours but cannot figure out why it doesn't work... If I take out the style and replace the first binding with the second resource it does display proper however the binding within the style will not work.
Does anybody have any idea why?
EDIT
I just tried this but no luck.
<ToggleButton Command="{Binding Path=Command}" Content="{Binding Path=DisplayName}">
<ToggleButton.Style>
<Style TargetType="ToggleButton">
<Style.Triggers>
<Trigger Property="IsChecked" Value="False">
<Setter Property="Template" Value="{Utilities:BindableResource {Binding Path=TemplateResource}}" />
</Trigger>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Template" Value="{Utilities:BindableResource {Binding Path=SelectedTemplateResource}}" />
</Trigger>
</Style.Triggers>
</Style>
</ToggleButton.Style>
</ToggleButton>
After finding out this really is not possible to do in pure xaml I brought out the c# and create a custom control... this is very basic and can be improved on and I will have change a bit of it but ultimately a custom control solves the issue so that you can hit the click event from within the resource dictionary and change the template on the fly.
public class TabButton : Button
{
public static readonly DependencyProperty SelectedTemplateProperty =
DependencyProperty.Register("SelectedTemplate", typeof(ControlTemplate), typeof(TabButton));
public ControlTemplate SelectedTemplate
{
get { return base.GetValue(SelectedTemplateProperty) as ControlTemplate; }
set { base.SetValue(SelectedTemplateProperty, value); }
}
public TabButton()
{
this.Click += new RoutedEventHandler(TabButton_Click);
}
~TabButton()
{
}
public void TabButton_Click(object sender, RoutedEventArgs e)
{
ControlTemplate template = (ControlTemplate)this.FindResource("Environmental Template Selected");
(sender as TabButton).Template = template;
}
}
Cheers.

"Tag" ... Special functionality in WPF?

MSDN says "Gets or sets an arbitrary object value that can be used to store custom information about this element." which means I can store anything I want in this property.
But if you bind to this property (with property of type String having a value say "XYZ") and use it in Trigger conditions it doesn't work!
<Trigger Property="Tag" Value="XYZ">
<Setter Property="Background" Value="Red" />
</Trigger>
It does not set the background red. You can try and assume myElement to be a TextBlock! Why is it like this?
Tag has no special functionality in WPF.
This works for me:
<TextBlock Tag="{Binding Data}"
x:Name="tb">
<TextBlock.Style>
<Style>
<Style.Triggers>
<Trigger Property="TextBlock.Tag"
Value="XYZ">
<Setter Property="TextBlock.Background"
Value="Lime" />
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
And setting the Data object property to "XYZ" in an event.
The Tag is a construct held over from Winforms days (and possibly there from before that!). It was used as a convenient place to associate an object with a UI element, such as a FileInfo with a Button, so in the Button's event handler you could simply take the event sender, cast it to a Button, then cast the Tag value to a FileInfo and you have everything you need about the file you want to open.
There is one situation, however, where I've found the Tag is useful in WPF. I've used it as a holding spot that can be accessed by a ContextMenu MenuItem, which can't use the normal RelativeSource bindings you'd use to traverse the visual tree.
<ListBox.ItemContainerStyle>
<Style
TargetType="ListBoxItem">
<Setter
Property="Tag"
Value="{Binding ElementName=TheUserControlRootElement}" />
<Setter
Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem
Header="_Remove"
ToolTip="Remove this from this list"
CommandParameter="{Binding DataContext, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
Command="{Binding PlacementTarget.Tag.Remove, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
From the ContextMenu, I cannot access the Remove command which is defined in the UserControl class where this snippet is defined. But I can bind the root to the Tag of the ListBoxItem, which I can access via the ContextMenu.PlacementTarget property. The same trick can be used when binding within a ToolTip, as the same limitations apply.
MainWindow.xaml:
<Window x:Class="wpftest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBlock x:Name="test" MouseDown="test_MouseDown"
Tag="{Binding TestProperty}">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<Trigger Property="Tag" Value="XYZ">
<Setter Property="Background" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
</Window>
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new TestViewModel();
}
private void test_MouseDown(object sender, MouseButtonEventArgs e)
{
((TestViewModel)DataContext).TestProperty = "XYZ";
}
private sealed class TestViewModel : INotifyPropertyChanged
{
private string _testPropertyValue;
public string TestProperty
{
get { return _testPropertyValue; }
set
{
_testPropertyValue = value;
var handler = PropertyChanged;
if(handler != null)
handler(this, new PropertyChangedEventArgs("TestProperty"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
Updated: Tag property now is bound to TestProperty.

How turn-off tooltips for the whole application

Is it possible to turn-off toooltips for all controls (always or based on some rule) without setting TooltipService.IsEnabled on each control? I mean, going through all logical items takes too much time.
Try this. It hides all tooltips.
<Style TargetType="{x:Type ToolTip}">
<Setter Property="Visibility"
Value="Collapsed" />
</Style>
There are several ways you should be able to use to accomplish this. Marco Zhou outlines two of them in this posting., both of these methods relying on setting TooltipService.IsEnabled to False for a parent control such as a Window. Apparently it inherits to all children, so you can set it just there to disable all tooltips.
You could also set all of your Tooltips to a style which had bindings to a property that would make them invisible or disabled when you wanted.
EDIT
Adding the Code to make it easier to understand:
Create the ToolTipEnabled Attached Property which sets the FrameworkPropertyMetadataOptions.Inherits so that it will be inherited by the children.
public class ToolTipBehavior
{
public static Boolean GetIsToolTipEnabled(FrameworkElement obj)
{
return (Boolean)obj.GetValue(ToolTipEnabledProperty);
}
public static void SetToolTipEnabled(FrameworkElement obj, Boolean value)
{
obj.SetValue(ToolTipEnabledProperty, value);
}
public static readonly DependencyProperty ToolTipEnabledProperty = DependencyProperty.RegisterAttached(
"IsToolTipEnabled",
typeof(Boolean),
typeof(ToolTipBehavior),
new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.Inherits, (sender, e) =>
{
FrameworkElement element = sender as FrameworkElement;
if (element != null)
{
element.SetValue(ToolTipService.IsEnabledProperty, e.NewValue);
}
}));
}
You can either use this property in the XAML or codebehind as below:
<Window x:Class="AnswerHarness.ToggleToolTipsDemo"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cc="clr-namespace:AnswerHarness"
Title="ToggleToolTipsDemo" Height="300" Width="300" Name="window">
<StackPanel>
<CheckBox IsChecked="{Binding Path=(cc:ToolTipBehavior.IsToolTipEnabled), ElementName=window}" Content="Enable ToolTip"/>
<Border BorderBrush="Green" BorderThickness="1" Background="Yellow" ToolTip="Border">
<StackPanel>
<Button Width="120" Height="30" Content="Button1" ToolTip="Button1"/>
<Button Width="120" Height="30" Content="Button2" ToolTip="Button2"/>
<Button Width="120" Height="30" Content="Button3" ToolTip="Button3"/>
</StackPanel>
</Border>
</StackPanel>
</Window>
Or
public partial class ToggleToolTipsDemo : Window
{
public ToggleToolTipsDemo()
{
InitializeComponent();
// You can programmatically disable tool tip here.
this.SetValue(ToolTipBehavior.ToolTipEnabledProperty, false);
}
}
Put this style where it is accessible throughout the application(a resourcedictionary or App.xaml) so you won't need to reference this style in any textbox.
<Style BasedOn="{x:Null}" TargetType="{x:Type TextBox}">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/>
<Setter Property="BorderBrush" Value="{StaticResource TextBoxBorder}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="1"/>
<Setter Property="AllowDrop" Value="true"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst"/>
<Setter Property="Stylus.IsFlicksEnabled" Value="False"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Microsoft_Windows_Themes:ListBoxChrome x:Name="Bd" ToolTipService.IsEnabled="False" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" RenderMouseOver="{TemplateBinding IsMouseOver}" RenderFocused="{TemplateBinding IsKeyboardFocusWithin}" SnapsToDevicePixels="true">
<ScrollViewer ToolTipService.IsEnabled="False" x:Name="PART_ContentHost" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</Microsoft_Windows_Themes:ListBoxChrome>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
<Trigger Property="Text" Value="">
<Setter Property="ToolTipService.IsEnabled" Value="False"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
NOTE
This is the default textbox style generated by Expression blend to which I have added the following trigger which enables tooltips when textbox is not empty and disables them otherwise
<Trigger Property="Text" Value="">
<Setter Property="ToolTipService.IsEnabled" Value="False"/>
</Trigger>
I don't know of any global setting, but there is an easy way to 'visit' all of the elements of your visual tree using Linq-to-VisualTree, I utility I wrote a while back that providers a Linq-to-XML style API for the visual tree.
The following should do the trick:
foreach(var element in window.Descendants())
ToolttipService.SetIsEnabled(element, false);
You can try to use
ToolTipService.IsOpenProperty.OverrideMetadata(typeof(DependencyObject),new PropertyMetadata(false));
I don't have an answer for handling the entire app in one statement, but I've been able to centralize a number of UI-specific parameters in a general base class, then create applications which are derived off this base class and inherit the centralized settings. I should mention there's some extra plumbing you have to add to the base class to support MVVM as in the following:
public class MyMainWindowBaseClass : Window, INotifyPropertyChanged
{
...whatever unrelated stuff you need in your class here...
private int m_toolTipDuration = 3000; // Default to 3 seconds
public int MyToolTipDuration
{
get { return m_toolTipDuration; }
set
{
if (m_toolTipDuration != value)
{
bool transition = (value == 0 || m_toolTipDuration == 0);
m_toolTipDuration = value;
NotifyPropertyChanged("MyToolTipDuration");
if (transition)
{
NotifyPropertyChanged("MyToolTipEnabled");
}
}
}
}
public bool MyToolTipEnabled
{
get { return (m_toolTipDuration > 0); }
}
public event PropertyChangedEventHandler PropertyChanged;
... whatever variables, properties, methods, etc., you need here...
///-----------------------------------------------------------------------------
/// <summary>
/// Fires property-changed event notification
/// </summary>
/// <param name="propertyName">The name of the property that changed</param>
///-----------------------------------------------------------------------------
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
The XAML code looks like this:
<Button Command="{Binding StartCommand}"
Content="Start"
FontWeight="Bold"
Height="Auto"
HorizontalAlignment="Left"
Margin="20,40,0,0"
Name="ui_StartButton"
ToolTip="Click this button to begin processing."
ToolTipService.IsEnabled="{Binding RelativeSource={RelativeSource AncestorType=Window},Path=MyToolTipEnabled}"
ToolTipService.ShowDuration="{Binding RelativeSource={RelativeSource AncestorType=Window},Path=MyToolTipDuration}"
VerticalAlignment="Top"
Width="90"/>
With the important bindings being those related to ToolTipService.IsEnabled and ToolTipService.ShowDuration.
You can see that if MyToolTipDuration is set to zero, MyToolTipEnabled will return false and this disables the tooltip. In my first attempt I tried simply setting MyToolTipDuration to zero without using the ToolTipService.IsEnabled= in conjunction with the MyToolTipEnabled property, but all that accomplished was flashing, barely-readable tooltips which appear and disappear.
Overall this worked pretty well for me (ymmv), though not as well as a single setting or single call that would have handled the entire app and circumvented the need for distributing these bindings into every item with a tooltip I wanted to be support the ability to disable. Oh well, when in Rome....
In any event, hopefully someone finds this of use.

Resources