Binding ToolTip to DependencyProperty inside custom control - c#

I am trying to bind some values to ToolTipService.ShowDuration and some other ToolTip's properties in a CellStyle of DataGridTextColumn.
Normaly, I am doing something like this:
<UserControl
...namespace declarations...>
<UserControl.Resources>
<mycontrols:BindingProxy x:Key="proxy" Data="{Binding MySettings}"/>
</UserControl.Resources>
<DataGrid>
<DataGridTextColumn
Binding="{Binding SomeBinding}">
<DataGridTextColumn.CellStyle>
<Style
TargetType="DataGridCell"
BasedOn="{StaticResource ResourceKey={x:Type DataGridCell}}">
<Setter
Property="ToolTipService.ShowDuration"
Value="{Binding Data.ToolTipDuration, Source={StaticResource proxy}}"/>
<Setter Property="ToolTip">
<Setter.Value>
<TextBlock
Text="{Binding SomeBinding}"
MaxWidth="{Binding Data.ToolTipMaxWidth, Source={StaticResource proxy}}"
TextWrapping="Wrap" TextTrimming="CharacterEllipsis"/>
</Setter.Value>
</Setter>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
</DataGrid>
</UserControl>
Since ToolTip has it's own visual tree, I am using binding proxy like this:
public class BindingProxy : Freezable
{
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
// Using a DependencyProperty as the backing store for Data. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}
Up to this point, all works as expected. But I wanted to re-use this DataGridTextColumn, so I created new file like this:
<DataGridTextColumn
x:Class="Test.MyControls.DataGridLargeTextColumn"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Test.MyControls">
<DataGridTextColumn.CellStyle>
<Style
TargetType="DataGridCell"
BasedOn="{StaticResource ResourceKey={x:Type DataGridCell}}">
<Setter
Property="ToolTipService.ShowDuration"
Value="{Binding ToolTipDuration}"/>
<Setter Property="ToolTip">
<Setter.Value>
<TextBlock
Text="{Binding SomeBinding}"
MaxWidth="{Binding ToolTipWidth}"
TextWrapping="Wrap" TextTrimming="CharacterEllipsis"/>
</Setter.Value>
</Setter>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
With code behinde:
public partial class DataGridLargeTextColumn : DataGridTextColumn
{
public int ToolTipDuration
{
get { return (int)GetValue(ToolTipDurationProperty); }
set { SetValue(ToolTipDurationProperty, value); }
}
public static readonly DependencyProperty ToolTipDurationProperty =
DependencyProperty.Register("ToolTipDuration", typeof(int), typeof(DataGridLargeTextColumn), new UIPropertyMetadata(default(int)));
public string SomeBinding
{
get { return (string)GetValue(SomeBindingProperty); }
set { SetValue(SomeBindingProperty, value); }
}
public static readonly DependencyProperty SomeBindingProperty =
DependencyProperty.Register("SomeBinding", typeof(string), typeof(DataGridLargeTextColumn), new UIPropertyMetadata(default(string)));
public int ToolTipWidth
{
get { return (int)GetValue(ToolTipWidthProperty); }
set { SetValue(ToolTipWidthProperty, value); }
}
public static readonly DependencyProperty ToolTipWidthProperty =
DependencyProperty.Register("ToolTipWidth", typeof(int), typeof(DataGridLargeTextColumn), new UIPropertyMetadata(default(int)));
public DataGridLargeTextColumn()
{
InitializeComponent();
}
}
This does't work because ToolTip has it's own visual tree, but since I have nowhere to put proxy, I don't know how to make it work or if it is even possible. I found this answer, and it seems to be on the right track, however, I tried to implement it like this with no luck:
<Setter
Property="ToolTipService.ShowDuration"
Value="{Binding Path=PlacementTarget.(local:DataGridLargeTextColumn.ToolTipDuration), RelativeSource={RelativeSource Self}}"/>
<Setter Property="ToolTip">
<Setter.Value>
<TextBlock
Text="{Binding Path=PlacementTarget.(local:DataGridLargeTextColumn.SomeBinding), RelativeSource={RelativeSource Self}}"
MaxWidth="{Binding Path=PlacementTarget.(local:DataGridLargeTextColumn.ToolTipWidth), RelativeSource={RelativeSource Self}}"
TextWrapping="Wrap" TextTrimming="CharacterEllipsis"/>
</Setter.Value>
</Setter>
Am I using PlacementTarget wrong? If not, why is it not working, and is there another solution?
UPDATE:
As per Mark's answer, I've modified the DataGridLargeTextColumn's Style:
<Style
TargetType="DataGridCell"
BasedOn="{StaticResource {x:Type DataGridCell}}">
<Setter
Property="ToolTipService.ShowDuration" Value="{Binding Path=PlacementTarget.ToolTipShowDuration, RelativeSource={x:Static RelativeSource.Self}}"/>
<Setter Property="ToolTip">
<Setter.Value>
<ToolTip DataContext="{Binding Path=PlacementTarget, RelativeSource={x:Static RelativeSource.Self}}">
<TextBlock
Text="{Binding DataContext.SomeBinding}"
MaxWidth="{Binding Column.ToolTipWidth}"
TextWrapping="Wrap" TextTrimming="CharacterEllipsis"/>
</ToolTip>
</Setter.Value>
</Setter>
</Style>
And I'm using that control like this:
<UserControl
...namespace declarations...>
<UserControl.Resources>
<mycontrols:BindingProxy x:Key="proxy" Data="{Binding MySettings}"/>
</UserControl.Resources>
<DataGrid>
<DataGrid.Columns>
<mycontrols:DataGridLargeTextColumn
Binding="{Binding SomeBinding}"
ToolTipShowDuration="{Binding Data.ToolTipDuration, Source={StaticResource proxy}}"
ToolTipWidth="{Binding Data.ToolTipMaxWidth, Source={StaticResource proxy}}"/>
</DataGrid.Columns>
</DataGrid>
</UserControl>
Width binding now works like a charm, but there are two problems I still cannot solve:
I cannot get tool tip's duration to bind, I have tried few different approches, but since it's abstract, it cannot be declared explicitly
ToolTip's Text property is set to SomeBinding, which is OK in this particular case, but I want to be able to set it to whatever, so I tried to declare it using DependencProperty from above like this:
Text="{Binding Column.ToolTipText}"
This works OK if i use it with string literal:
<myControls:DataGridLargeTextColumn
Binding="{Binding SomeBinding}"
ToolTipText="12345"
ToolTipShowDuration="{Binding Data.ToolTipDuration, Source={StaticResource proxy}}"
ToolTipWidth="{Binding Data.ToolTipMaxWidth, Source={StaticResource proxy}}"/>
But it doesn't work when I try to bind it, which is what I am trying to achieve:
<myControls:DataGridLargeTextColumn
Binding="{Binding SomeBinding}"
ToolTipText="{Binding SomeOtherPropertyBinding}"
ToolTipShowDuration="{Binding Data.ToolTipDuration, Source={StaticResource proxy}}"
ToolTipWidth="{Binding Data.ToolTipMaxWidth, Source={StaticResource proxy}}"/>

By default your ToolTip's DataContext gets set to the DataContext of the cell. You, however, are trying to bind to dependency properties in the cell's column instead, so you're going to have to change the DataContext to point to the cell itself and then reference DataContext explicitly when you want to access the data and Column when you want to access the DPs in your DataGridLargeTextColumn.
In other words, declare the ToolTip explicitly in addition to its content and set its DataContext, like this:
<Setter Property="ToolTip">
<Setter.Value>
<ToolTip DataContext="{Binding Path=PlacementTarget, RelativeSource={x:Static RelativeSource.Self}}">
<TextBlock
Text="{Binding DataContext.SomeBinding}"
Width="{Binding Column.ToolTipWidth}" />
</ToolTip>
</Setter.Value>
</Setter>
...where Text in this case is binding to the data and Width is binding to the custom column DP.
Alternatively you could also just leave the DataContext as is and instead use the ToolTip's Tag property as a binding proxy to the DataGridLargeTextColumn:
<Setter Property="ToolTip">
<Setter.Value>
<ToolTip Tag="{Binding Path=PlacementTarget.Column, RelativeSource={x:Static RelativeSource.Self}}">
<TextBlock
Text="{Binding SomeBinding}"
Width="{Binding Tag.ToolTipWidth, RelativeSource={RelativeSource AncestorType=ToolTip}}" />
</ToolTip>
</Setter.Value>
</Setter>

Related

Why is my custom ContentControl with data triggers not showing up in the UI, though the constructor is called?

I'm using reflection and expression trees in C# to build a fairly adaptable search tool for our database. Because of this, I have a need for a custom ContentControl - termed 'MultiStyleInputBox' - which uses data triggers to adjust its ContentTemplate to the Type of input expected. The problem is, while the code builds just fine and I have confirmed that both the public and static constructors are being hit when the code executes, the ContentControl's content doesn't show up at all in my UI.
Now, I'm relatively new to writing custom XAML/C# UI control classes, but I have been able to cobble together the following:
<ContentControl x:Class="MyApp.MultiStyleInputBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Name="multiStyleInputBox">
<ContentControl.Resources>
<Style TargetType="ContentControl" BasedOn="{StaticResource {x:Type ContentControl}}">
<Style.Triggers>
<DataTrigger Binding="{Binding InputType, ElementName=multiStyleInputBox}" Value="{x:Type sys:DateTime}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<DatePicker SelectedDate="{Binding Value, ElementName=multiStyleInputBox}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<!--I have several of these triggers for different data types-->
</Style.Triggers>
<Style.Setters>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBox Text="{Binding Value, ElementName=multiStyleInputBox}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
</ContentControl.Resources>
</ContentControl>
And the code behind:
public sealed partial class MultiStyleInputBox : ContentControl
{
//Dependency properties
public Type InputType
{
get { return (Type)GetValue(InputTypeProperty); }
set { SetValue(InputTypeProperty, value); }
}
public static readonly DependencyProperty InputTypeProperty =
DependencyProperty.Register("InputType", typeof(Type), typeof(MultiStyleInputBox));
public object Value
{
get { return (object)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(object), typeof(MultiStyleInputBox));
//Constructors
public MultiStyleInputBox() : base()
{
}
static MultiStyleInputBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MultiStyleInputBox),
new FrameworkPropertyMetadata(typeof(MultiStyleInputBox)));
}
}
I thought at one point that I might not have set the content of the ContentControl, and so I added a <ContentPresenter/>, but I received an error saying that the content is set more than once, so I believe that my <Style.Setters></Style.Setters> section is taking care of that. Otherwise, even running around using PresentationTraceSources.TraceLevel="High" on my bindings, I haven't so far been able to run into any useful errors.
Is there some sort of glaring issue in my code that I can immediately address (hopefully)? Do I need to reevaluate my approach to the problem?
Update
After suggested corrections in the answers below, here is the latest version of the code:
<ContentControl x:Class="MyApp.MultiStyleInputBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Name="multiStyleInputBox">
<ContentControl.Style>
<Style TargetType="ContentControl" BasedOn="{StaticResource {x:Type ContentControl}}">
<Style.Triggers>
<DataTrigger Binding="{Binding InputType, ElementName=multiStyleInputBox, Value="{x:Type sys:DateTime}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<DatePicker SelectedDate="{Binding Value, ElementName=multiStyleInputBox}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<!--I have several of these triggers for different data types-->
</Style.Triggers>
<Style.Setters>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBox Text="{Binding Value, ElementName=multiStyleInputBox}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
</ContentControl.Style>
</ContentControl>
And the code-behind:
public partial class MultiStyleInputBox : ContentControl
{
//Dependency properties
public Type InputType
{
get { return (Type)GetValue(InputTypeProperty); }
set { SetValue(InputTypeProperty, value); }
}
public static readonly DependencyProperty InputTypeProperty =
DependencyProperty.Register("InputType", typeof(Type), typeof(MultiStyleInputBox));
public object Value
{
get { return GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(object), typeof(MultiStyleInputBox),
new FrameworkPropertyMetadata(DateTime.Now,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
//Constructors
public MultiStyleInputBox() : base()
{
}
static MultiStyleInputBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MultiStyleInputBox),
new FrameworkPropertyMetadata(typeof(MultiStyleInputBox)));
}
}
Here is a test instantiation of the MultiStyleInputBox (I'm using Mahapps.Metro):
<Controls:MetroWindow
xmlns:Controls="http://metro.mahapps.com/winfx/xaml/controls"
x:Class="MyApp.TestWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MyApp"
Title="Test Window" Height="450" Width="800"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<StackPanel>
<local:MultiStyleInputBox x:Name="TestMultiBox" Value="1" InputType="{x:Type sys:Int32}"/>
</StackPanel>
</Controls:MetroWindow>
When I try to instantiate this class, I'm still not getting anything showing up in my UI, and the ContentControl isn't taking up any space. Even if I include Width="50" Height="24", I still get nothing. I've tested setting both Value and InputType in code-behind and using a breakpoint to inspect the object, and I'm finding that, while both values get set, the Content of the ContentControl remains null.
The immediate problem is that your style isn't applied to the ContentControl you want to apply it to. You're defining an implicit ContentControl style which will be applied to any ContentControls you create in the content of this control -- but you aren't creating any, and that's not what you want anyhow.
For a quick fix, just change ContentControl.Resources to ContentControl.Style.
<ContentControl x:Class="MyApp.MultiStyleInputBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Name="multiStyleInputBox">
<ContentControl.Style>
<Style TargetType="ContentControl" BasedOn="{StaticResource {x:Type ContentControl}}">
<Style.Triggers>
<DataTrigger Binding="{Binding InputType, ElementName=multiStyleInputBox}" Value="{x:Type sys:DateTime}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<DatePicker SelectedDate="{Binding Value, ElementName=multiStyleInputBox}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<!--I have several of these triggers for different data types-->
</Style.Triggers>
<Style.Setters>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBox Text="{Binding Value, ElementName=multiStyleInputBox}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
</ContentControl.Style>
</ContentControl>
Your next problem will be that selecting a new DateTime in the DatePicker won't update a property bound to the Value property of your viewmodel. Here's the fix for that:
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(object), typeof(MultiStyleInputBox),
new FrameworkPropertyMetadata(DateTime.Now,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
And the last problem (or the first, perhaps) was that you weren't calling InitializeComponent() in the constructor, which is always required in any WPF codebehind class:
public MultiStyleInputBox()
{
InitializeComponent();
}

Displaying a tooltip for each Combobox item when the list is opened C# WPF XAML MVVM

I'm developing an application in C# MVVM
My question is about adding a tooltip for each item that the ComboBox is binded to. Since there are two items only I want it to show the tooltip whenever I open the dropdown and hover my mouse over one of the items , like this:
if I hover my mouse over the first element in the dropDown I'll get a ToolTip with " first item".. and "second item" when I hover over the second element.
ComboBox is placed in DataGridTemplateColumn -> Cell Template -> DataTemplate
<DataGridTemplateColumn Header="PRĄD POJEMNOŚCIOWY [A]" HeaderStyle="{StaticResource PRAD_POJEMNOSCIOWY}">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox Name="PradPojemnosciowyComboBox"
SelectedValue="{Binding SelectedItem, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding Path=LiniaWyComboBox, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
IsEditable="True"
IsReadOnly="False"
Text="{Binding Prad_pojemnosciowy, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
IsTextSearchEnabled="False"
IsSynchronizedWithCurrentItem="True"
PreviewKeyDown="PradPojemnosciowyComboBox_OnPreviewKeyDown">
<ComboBox.Style>
<Style TargetType="ComboBox">
<Style.Triggers>
<Trigger Property="SelectedValue" Value="{x:Null}">
<Setter Property="SelectedIndex" Value="{Binding LiniaWyComboBox}"/>
</Trigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
UPDATE
ToolTipLabel.cs:
using System.ComponentModel;
using System.Collections.ObjectModel;
namespace GPZmodel.UserControlsGraphicGenerators
{
public class ToolTipLabel : INotifyPropertyChanged
{
private string _toolTipText;
public string ToolTipText
{
get { return _toolTipText;}
set
{
if (_toolTipText != value)
{
_toolTipText = value;
}
}
}
public ObservableCollection<ToolTipLabel> ToolTipList = new ObservableCollection<ToolTipLabel>()
{
new ToolTipLabel() {ToolTipText = "Nazwa1"} ,
new ToolTipLabel() {ToolTipText = "Nazwa2"} ,
};
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
You could use an ItemContainerStyle:
<ComboBox Name="PradPojemnosciowyComboBox"
SelectedValue="{Binding SelectedItem, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding Path=LiniaWyComboBox, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
IsEditable="True"
IsReadOnly="False"
Text="{Binding Prad_pojemnosciowy, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
IsTextSearchEnabled="False"
IsSynchronizedWithCurrentItem="True"
PreviewKeyDown="PradPojemnosciowyComboBox_OnPreviewKeyDown">
<ComboBox.Style>
<Style TargetType="ComboBox">
<Style.Triggers>
<Trigger Property="SelectedValue" Value="{x:Null}">
<Setter Property="SelectedIndex" Value="{Binding LiniaWyComboBox}"/>
</Trigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="ToolTip">
<Setter.Value>
<TextBlock Text="{Binding}" />
</Setter.Value>
</Setter>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
Bind the TextBlock in the ToolTip to whatever property of your data object that you want to display.

WPF Using bindings to ViewModel in style used to change controls

The objective is to swap the control at runtime based on a property in my ViewModel, and have the displayed control be have a binding that updates the properties in the ViewModel. I started by creating the following Style in the View.xaml:
<UserControl.Resources>
<Style x:Key="DisplayTextOrButton" TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding TextNotButton}" Value="True">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBox Content="{Binding SomeText}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding TextNotButton}" Value="False">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Button Content="{Binding ButtonText}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
Note that the bindings LabelText and ButtonText are the bindings to the properties in the ViewModel.
Then further on in the View.xaml I have the following:
<ContentControl Content="{Binding TextNotButton}"
Style="{StaticResource DisplayTextOrButton}">
</ContentControl>
Finally, the ViewModel.cs has the following properties:
private bool textNotButton;
public bool TextNotButton
{
get => this.textNotButton;
set
{
this.textNoButton = value;
this.OnPropertyChanged("TextNotButton");
}
}
private string someText;
public string SomeText
{
get => this.someText;
set
{
this.someText = value;
this.OnPropertyChanged("SomeText");
}
}
private string buttonText;
public string ButtonText
{
get => this.buttonText;
set
{
this.buttonText = value;
this.OnPropertyChanged("ButtonText");
}
}
The style works well for swapping between the label and the button, but changing the text in the TextBox does not update the property in the ViewModel, and the Button's text is empty (I imagine because the binding hasn't worked)
I believe this is because the style is a static resource so the bindings SomeText and ButtonText in the style aren't actually the bindings in the ViewModel, but I'm not sure how to pass the reference of the other properties into the style. Or even if that's a thing. I'm pretty new to XAML so not sure on how to handle this

DataTrigger binded to property in ViewModel is not fired in the Button of DataGrid

I've created ControlTemplates:
<Window.Resources>
<ControlTemplate x:Key="imgNo" TargetType="{x:Type Control}">
<Image Source="pack://application:,,,/Images/up.png"/>
</ControlTemplate>
<ControlTemplate x:Key="imgUp" TargetType="{x:Type Control}">
<!--<TextBlock Text="Up"/>-->
<Image Source="pack://application:,,,/Images/up.png"/>
</ControlTemplate>
<ControlTemplate x:Key="imgDown" TargetType="{x:Type Control}">
<Image Source="pack://application:,,,/Images/downArrow.png"/>
</ControlTemplate>
<DataTemplate x:Key="ButtonOneDataTemplate">
<Control x:Name="theControl" Template="{DynamicResource imgNo}" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsImageChanged}" Value="true">
<Setter TargetName="theControl" Property="Template" Value="{DynamicResource imgUp}" />
</DataTrigger>
<DataTrigger Binding="{Binding IsImageChanged}" Value="false">
<Setter TargetName="theControl" Property="Template" Value="{DynamicResource imgDown}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</WindowResources>
and Button in DataGrid which uses above ControlTemplates:
<DataGrid ItemsSource="{Binding Persons}" Grid.Row="1" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding IdPerson}">
<DataGridTextColumn.HeaderTemplate>
<DataTemplate>
<Border Background="Violet">
<StackPanel>
<Button ContentTemplate="{StaticResource ButtonOneDataTemplate}"
Command="{Binding DataContext.HelloCommand, RelativeSource=
{RelativeSource AncestorType=Window}}"
CommandParameter="{Binding DataContext.Hello,
RelativeSource={RelativeSource AncestorType=DataGrid}}"/>
</StackPanel>
</Border>
</DataTemplate>
</DataGridTextColumn.HeaderTemplate>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
My ViewModel:
public class MainWindowViewModel:ViewModelBase
{
public RelayCommand HelloCommand { get; set; }
public MainWindowViewModel()
{
LoadPersons();
HelloCommand = new RelayCommand(SayHello);
}
int helloCounter = 0;
private void SayHello(object obj)
{
if (helloCounter % 2 == 0)
IsImageChanged = true;
else
IsImageChanged = false;
helloCounter++;
}
private bool isImageChanged=true;
public bool IsImageChanged
{
get { return isImageChanged; }
set { isImageChanged = value;
OnPropertyChanged("IsImageChanged");
}
}
}
What I want is when I click on the button <Button ContentTemplate="{StaticResource ButtonOneDataTemplate}"/>, then Template should be replaced to {DynamicResource imgDown} or {DynamicResource imgUp}. DataTrigger depends on IsImageChanged value.
However, if I click on the Button, then DataTrigger is not fired(Controltemplates such as imgUp, imgDown are not changed). How can I achieve this from my ViewModel?
Problem is that DataGrid column is not a part of a visual tree, and because of that it does not inherit DataContext. To be able to use DataTriggers in your ButtonOneDataTemplate you need that button, you applying this template to, has correct DataContext. There is a trick, how to provide DataContext to elements that are not in VisualTree, described here
Applying that solution to your code we'll have the following:
Proxy
public class BindingProxy : Freezable
{
#region Overrides of Freezable
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
#endregion
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
// Using a DependencyProperty as the backing store for Data. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}
Window.Resources
<local:BindingProxy x:Key="proxy" Data="{Binding}" />
<DataTemplate x:Key="ButtonOneDataTemplate">
<Control x:Name="theControl" Template="{DynamicResource imgNo}" Foreground="Orange"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=DataContext.IsImageChanged}" Value="True">
<Setter TargetName="theControl" Property="Template" Value="{DynamicResource imgUp}" />
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=DataContext.IsImageChanged}" Value="False">
<Setter TargetName="theControl" Property="Template" Value="{DynamicResource imgDown}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
HeaderTemplate
<DataGridTextColumn.HeaderTemplate>
<DataTemplate>
<Border Background="Violet">
<StackPanel>
<Button ContentTemplate="{StaticResource ButtonOneDataTemplate}"
DataContext="{Binding Path=Data, Source={StaticResource proxy}}"
Command="{Binding DataContext.ButtonClick, RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="{Binding DataContext, RelativeSource={RelativeSource AncestorType=DataGrid}}"/>
</StackPanel>
</Border>
</DataTemplate>
</DataGridTextColumn.HeaderTemplate>

<Dependency> Property was already registered by <type>

I know this question was already asked by many peoples. I got answer for that also. but somehow the DP which I had created is not setting properly in my styles.
Here is my code. [Listing-1]
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Style TargetType="{x:Type local:DataGridColumnFilter}">
<Style.Resources>
<local:BooleanToHeightConverter x:Key="booleanToHeightConverter" />
<local:FontSizeToHeightConverter x:Key="fontSizeToHeightConverter" />
<local:MyOppositeBooleanToVisibilityConverter x:Key="oppositeBooleanToVisibilityConverter" />
<local:ClearFilterButtonVisibilityConverter x:Key="clearFilterButtonVisibilityConverter" />
<Style TargetType="{x:Type local:DelayTextBox}">
<Setter Property="Background" Value="AliceBlue" />
</Style>
</Style.Resources>
<Setter Property="Height" Value="{Binding RelativeSource={RelativeSource Self}, Path=IsControlInitialized, Converter={StaticResource booleanToHeightConverter}}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:DataGridColumnFilter}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid Name="PART_FilterContainer"
Visibility="{Binding Path=AssignedDataGridColumn.DoNotGenerateFilterControl, RelativeSource={RelativeSource AncestorType={x:Type local:DataGridColumnFilter}}, Converter={StaticResource oppositeBooleanToVisibilityConverter}}"
>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- Clear Filter Button -->
<Button
VerticalAlignment="Top"
Height="{Binding
RelativeSource={RelativeSource AncestorType={x:Type DataGrid}},
Converter={StaticResource fontSizeToHeightConverter},
Path=FontSize}"
ToolTip="Clear filter"
Command="{Binding Path=(local:DataGridExtensions.ClearFilterCommand), RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"
Grid.Column="0" Name="PART_ClearFilterButton">
<Button.Content>
Clear filter
</Button.Content>
<Button.Visibility>
<MultiBinding Converter="{StaticResource clearFilterButtonVisibilityConverter}">
<Binding Path="IsFirstFilterControl" RelativeSource="{RelativeSource TemplatedParent}"/>
<Binding Path="(local:DataGridExtensions.IsClearButtonVisible)" RelativeSource="{RelativeSource AncestorType={x:Type DataGrid}}"/>
</MultiBinding>
</Button.Visibility>
</Button>
<!-- Clear Filter Button -->
<!-- Numeric and Text-->
<local:DelayTextBox
Visibility="Collapsed"
VerticalAlignment="Top"
VerticalContentAlignment="Center"
Height="{Binding
RelativeSource={RelativeSource AncestorType={x:Type DataGrid}},
Converter={StaticResource fontSizeToHeightConverter},
Path=FontSize}"
Text="{Binding
RelativeSource={RelativeSource AncestorType={x:Type local:DataGridColumnFilter}},
Path=FilterCurrentData.QueryString,
Mode=TwoWay,
UpdateSourceTrigger=Explicit}"
Grid.Column="2" Name="PART_TextBoxFilter">
</local:DelayTextBox>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsTextFilterControl" Value="true">
<Setter TargetName="PART_TextBoxFilter" Property="Visibility" Value="Visible"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Style.Resources>
<local:BooleanToHeightConverter x:Key="booleanToHeightConverter" />
<local:FontSizeToHeightConverter x:Key="fontSizeToHeightConverter" />
<local:MyOppositeBooleanToVisibilityConverter x:Key="oppositeBooleanToVisibilityConverter" />
<local:ClearFilterButtonVisibilityConverter x:Key="clearFilterButtonVisibilityConverter" />
<!--<Style TargetType="{x:Type local:DelayTextBox}">
<Setter Property="Background" Value="AliceBlue" />
</Style>-->
</Style.Resources>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridColumnHeader}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<local:DataGridColumnFilter Margin="1" Grid.Column="0" Grid.Row="0"
FilterCurrentData ="{Binding
RelativeSource={RelativeSource AncestorType={x:Type DataGridColumnHeader}},
Path=Tag,
UpdateSourceTrigger=PropertyChanged,
Mode=TwoWay}"
AssignedDataGridColumnHeader ="{Binding
RelativeSource={RelativeSource AncestorType={x:Type DataGridColumnHeader}},
Path=.}"
AssignedDataGridColumn ="{Binding
RelativeSource={RelativeSource AncestorType={x:Type DataGridColumnHeader}},
Path=Column}"
DataGrid ="{Binding
RelativeSource={RelativeSource AncestorType={x:Type DataGrid}},
Path=.}"
DataGridItemsSource ="{Binding
RelativeSource={RelativeSource AncestorType={x:Type DataGrid}},
Path=ItemsSource}"
>
</local:DataGridColumnFilter>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<DataGrid ItemsSource="{Binding ListOfStudents,Mode=TwoWay}"
>
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Path=Name}" local:DataGridColumnExtensions.DoNotGenerateFilterControl="True"
/>
<DataGridTextColumn Header="Address" Binding="{Binding Path=Address}" local:DataGridColumnExtensions.DoNotGenerateFilterControl="true"
/>
<DataGridTextColumn Header="Tel" Binding="{Binding Path=Tel}"
/>
</DataGrid.Columns>
</DataGrid>
</Grid>
I'm getting error when I initialize property as follows :- [Listing-2]
public class DataGridColumnExtensions
{
public static DependencyProperty IsCaseSensitiveSearchProperty =
DependencyProperty.RegisterAttached("IsCaseSensitiveSearch",
typeof(bool), typeof(DataGridColumn));
public static bool GetIsCaseSensitiveSearch(DependencyObject target)
{
return (bool)target.GetValue(IsCaseSensitiveSearchProperty);
}
public static void SetIsCaseSensitiveSearch(DependencyObject target, bool value)
{
target.SetValue(IsCaseSensitiveSearchProperty, value);
}
public static DependencyProperty IsBetweenFilterControlProperty =
DependencyProperty.RegisterAttached("IsBetweenFilterControl",
typeof(bool), typeof(DataGridColumn));
public static bool GetIsBetweenFilterControl(DependencyObject target)
{
return (bool)target.GetValue(IsBetweenFilterControlProperty);
}
public static void SetIsBetweenFilterControl(DependencyObject target, bool value)
{
target.SetValue(IsBetweenFilterControlProperty, value);
}
public static DependencyProperty DoNotGenerateFilterControlProperty =
DependencyProperty.RegisterAttached("DoNotGenerateFilterControl",
typeof(bool), typeof(DataGridColumn), new PropertyMetadata(false));
public static bool GetDoNotGenerateFilterControl(DependencyObject target)
{
return (bool)target.GetValue(DoNotGenerateFilterControlProperty);
}
public static void SetDoNotGenerateFilterControl(DependencyObject target, bool value)
{
target.SetValue(DoNotGenerateFilterControlProperty, value);
}
}
}
After searching on StackOverflow I found that I should declare property as follows [Listing-3]
public class DataGridColumnExtensions
{
public static DependencyProperty IsCaseSensitiveSearchProperty =
DependencyProperty.RegisterAttached("IsCaseSensitiveSearch",
typeof(bool), typeof(DataGridColumnExtensions));
public static bool GetIsCaseSensitiveSearch(DependencyObject target)
{
return (bool)target.GetValue(IsCaseSensitiveSearchProperty);
}
public static void SetIsCaseSensitiveSearch(DependencyObject target, bool value)
{
target.SetValue(IsCaseSensitiveSearchProperty, value);
}
public static DependencyProperty IsBetweenFilterControlProperty =
DependencyProperty.RegisterAttached("IsBetweenFilterControl",
typeof(bool), typeof(DataGridColumnExtensions));
public static bool GetIsBetweenFilterControl(DependencyObject target)
{
return (bool)target.GetValue(IsBetweenFilterControlProperty);
}
public static void SetIsBetweenFilterControl(DependencyObject target, bool value)
{
target.SetValue(IsBetweenFilterControlProperty, value);
}
public static DependencyProperty DoNotGenerateFilterControlProperty =
DependencyProperty.RegisterAttached("DoNotGenerateFilterControl",
typeof(bool), typeof(DataGridColumnExtensions), new PropertyMetadata(false));
public static bool GetDoNotGenerateFilterControl(DependencyObject target)
{
return (bool)target.GetValue(DoNotGenerateFilterControlProperty);
}
public static void SetDoNotGenerateFilterControl(DependencyObject target, bool value)
{
target.SetValue(DoNotGenerateFilterControlProperty, value);
}
}
Then error from XAML disappears but I cannot get value of property DoNotGenerateFilterControl in my style where I'm setting visibility of Grid named as "PART_FilterContainer" (In Style ).
Also value of property was applied to my grid when I use Incorrect Dependency property code [Listing-2] but then XAML throws an error.
This is my first time to post question on StackOverflow. So please forgive as I had posted whole code.
Since DoNotGenerateFilterControl is an attached property, you should reference it as such when binding to it. Attached properties should be enclosed in parentheses, so instead of binding to
AssignedDataGridColumn.DoNotGenerateFilterControl
You should bind to
AssignedDataGridColumn.(local:DataGridColumnExtensions.DoNotGenerateFilterControl)
<Grid Name="PART_FilterContainer"
Visibility="{Binding Path=AssignedDataGridColumn.(local:DataGridColumnExtensions.DoNotGenerateFilterControl), RelativeSource={RelativeSource AncestorType={x:Type local:DataGridColumnFilter}}, Converter={StaticResource oppositeBooleanToVisibilityConverter}}">
This might be a workaround:
Maybe instead you could use a DataTrigger in XAML to control the visibility of the grid. Something like this:
<Grid Name="PART_FilterContainer">
<Grid.Style>
<Style TargetType="Grid">
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=DoNotGenerateFilterControl, ElementName=WhatEverElement}" Value="True">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
</Grid>

Categories

Resources