WPF Event Not Raised when Dependency Property Changed - c#

I have the following custom control based on the "heavy option" at this link:
public partial class SelectableContentControl : ContentControl
{
public SelectableContentControl()
{
InitializeComponent();
var isCheckedDesc = DependencyPropertyDescriptor.FromProperty(IsCheckedProperty, typeof(SelectableContentControl));
isCheckedDesc.AddValueChanged(this, IsCheckedPropertyChanged);
}
public bool IsChecked
{
get { return (bool)GetValue(IsCheckedProperty); }
set { SetValue(IsCheckedProperty, value); }
}
public static readonly DependencyProperty IsCheckedProperty =
DependencyProperty.Register("IsChecked", typeof(bool),
typeof(SelectableContentControl), new PropertyMetadata(false));
private void IsCheckedPropertyChanged(object sender, EventArgs e)
{
var selectable = Content as IAmSelectable;
if (selectable != null) selectable.IsSelected = IsChecked;
}
}
The style defined for the SelectableContentControl is as follows:
<Style TargetType="{x:Type controls1:SelectableContentControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls1:SelectableContentControl}">
<CheckBox IsChecked="{TemplateBinding IsChecked}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
...and my usage:
<controls:SelectableContentControl Grid.Row="2" Content="{Binding Dummy}" IsChecked="{Binding Dummy.IsChecked, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
I want IsCheckedPropertyChanged to be called whenever the IsChecked value changes on the UI, but this isn't happening. Anyone see what I'm missing?

TemplateBinding works in a OneWay mode, meaning that the value is updated only in source-to-target direction (your control being the source, and the CheckBox inside the template the target). If you want the binding to work in TwoWay mode, you should use a normal Binding instead:
<ControlTemplate TargetType="{x:Type controls1:SelectableContentControl}">
<CheckBox IsChecked="{Binding IsChecked, RelativeSource={RelativeSource TemplatedParent}}" />
</ControlTemplate>
Note that you don't need to specify Mode=TwoWay on the binding, because CheckBox.IsChecked property binds in two-way mode by default.
See this question for more detailed info.

Related

Change the value of a custom Dependency Property programmatically

I want to change the value of a custom Dependency Property programmatically.
This is my XAML:
<Window.Resources>
<ResourceDictionary>
<Style
x:Key="TreeViewItemStyle"
TargetType="TreeViewItem">
<Style.Triggers>
<Trigger
Property="local:ColorHelper.IsColor"
Value="True" >
<Setter
Property="Foreground"
Value="{Binding Color}" />
</Trigger>
<Trigger
Property="local:ColorHelper.IsColor"
Value="False" >
<Setter
Property="Foreground"
Value="Black" />
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
</Window.Resources>
I want this style to be applied to a treeview
<TreeView
HorizontalAlignment="Stretch"
Margin="15,65,15,0"
x:Name="treeView1"
VerticalAlignment="Stretch"
ItemContainerStyle="{StaticResource TreeViewItemStyle}"
ItemTemplate="{StaticResource CheckBoxItemTemplate}"
Grid.ColumnSpan="1"
Grid.RowSpan="2"
Grid.Column="1" />
And changing the color value of the IsColor property through a checkbox:
<CheckBox
Name="CHK_Gray"
VerticalAlignment="Center"
Foreground="DarkGray"
Grid.Row="6"
Grid.Column="0"
Grid.RowSpan="1"
Grid.ColumnSpan="2"
Unchecked="grayCheckBox_Unchecked"
Checked="grayCheckBox_Checked">
Show Created Assembly (in Grey)
</CheckBox>
The Dependency Property is created like this:
public class ColorHelper : DependencyObject
{
public static readonly DependencyProperty IsColorProperty = DependencyProperty.Register(
"IsColor", typeof(bool), typeof(ColorHelper), new PropertyMetadata(false));
public static void SetIsColor(DependencyObject target, Boolean value)
{
target.SetValue(IsColorProperty, value);
}
public static bool GetIsColor(DependencyObject target)
{
return (bool)target.GetValue(IsColorProperty);
}
}
How can i do in the Checked and Unchecked event to change the value of the IsColor property?
private void grayCheckBox_Checked(object sender, RoutedEventArgs e)
{
???
}
private void grayCheckBox_Unchecked(object sender, RoutedEventArgs e)
{
???
}
Thank you very much for your help!
You need to get a reference to the element of which you want to set the attached property to begin with.
You could try this method to get the TreeViewItem elements of the TreeView and then set the property of all of them if that's that you want:
foreach(TreeViewItem tvi i tv.FindTreeViewItems())
ColorHelper.SetIsColor(tvi, true);
Note that you cannot edit the template programmatically. And even if you could, it wouldn't really help as it has already been applied to the actual elements.

Dependency property is not updating the view

I have an ItemsControl that is binding to an ObservableCollection "MenuButtons".
In the ItemsControl, I want to add some Buttons programmatically with Dependency Properties.
My problem is that the values I pass are not updated. The default values are displayed in the view.
C#
private void btnTest_Click(object sender, RoutedEventArgs e)
{
var vm = DataContext as UflMainWindowViewModel;
vm.MenuButtons.Add(new UflMenuButton { IconText="Test123", Style = (Style)Application.Current.Resources["UflMenuButtonStyle"] });
}
C# UflButtonClass
public class UflMenuButton : Button
{
public string IconText
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("IconText", typeof(string), typeof(UflMenuButton), new UIPropertyMetadata("default", new PropertyChangedCallback(IconTextChanged)));
private static void IconTextChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
UflMenuButton button = (UflMenuButton)sender;
button.IconText = (string)e.NewValue;
}
}
with the following Style:
WPF
<Style x:Key="UflMenuButtonStyle" TargetType="{x:Type local:UflMenuButton}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Path=(local:UflMenuButton.IconText),RelativeSource={RelativeSource TemplatedParent}}" HorizontalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
You can bind to the first ancestor of type UflMenuButton :
<TextBlock Text="{Binding IconText, RelativeSource={RelativeSource AncestorType={x:Type local:UflMenuButton}}}" HorizontalAlignment="Center"/>
This work, but I assume a more elegant way exists.

Access custom property inside ContentTemplate of custom user control

I have the following custom user control:
namespace MyApp.Controls {
public partial class ArticleButton: UserControl {
public ArticleButton () {
InitializeComponent ();
}
public static readonly DependencyProperty TitleProperty;
static ArticleButton () {
TitleProperty = DependencyProperty.RegisterAttached ("Title",
typeof (String), typeof (ArticleButton));
}
[Description ("The name of the article."), Category ("Common Properties")]
public String Title {
get { return "TEST"; }
}
}
}
And the corresponding XAML:
<UserControl x:Class="MyApp.Controls.ArticleButton"
Name="UC"
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:local="clr-namespace:MyApp.Controls">
<Button Name="button" Click="button_Click" Style="{StaticResource defaultButtonStyle}">
<Button.ContentTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Path=Title, ElementName=UC}" />
</Grid>
</DataTemplate>
</Button.ContentTemplate>
</Button>
</UserControl>
Where defaultButtonStyle is defined in App.xaml (there is more than that, but this should be sufficient):
<Style TargetType="Button" x:Key="defaultButtonStyle">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Name="border"
BorderThickness="1"
Padding="4,2"
BorderBrush="DarkGray"
CornerRadius="3"
Background="{TemplateBinding Background}">
<Grid>
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
My problem is that the property Title is not displayed, I tried the following which did not work either:
<TextBlock Text="{Binding Path=Title, ElementName=UC}" />
<TextBlock Text="{Binding Path=Title, RelativeSource={RelativeSource TemplatedParent}}" />
<TextBlock Text="{Binding Path=Title, RelativeSource={RelativeSource AncestorType={x:Type local:ArticleButton}}}" />
I found lots of posts with similar issue, but none of them helped... I think the problem is that I try to access a custom property of the custom user control inside the content template of the inner button, but how to do that.
To sum up comments your 3rd RelativeSource binding
<TextBlock Text="RelativeSource={RelativeSource AncestorType={x:Type local:ArticleButton}}"/>
should work fine just use Register instead of RegisterAttached. As for other bindings {Binding Path=Title, ElementName=UC} won't work because ConrtrolTemplate has its own name scope and {Binding Path=Title, RelativeSource={RelativeSource TemplatedParent}}" won't work because you don't set property against Button but UserControl.
Another problem is that you set default value against CLR wrapper and, as mentioned on this MSDN page CLR wrapper is ignored and GetValue/SetValue methods are called directly for TitleProperty
The WPF XAML processor uses property system methods for dependency properties when loading binary XAML and processing attributes that are dependency properties. This effectively bypasses the property wrappers. When you implement custom dependency properties, you must account for this behaviour and should avoid placing any other code in your property wrapper other than the property system methods GetValue and SetValue.
If you want to specify default value and/or property changed callback then when you create DependencyProperty there is another variant of Register method which also takes PropertyMetadata parameter where you can set these values.
Another thing you need to be aware when using dependency properties is that, because its definition is static, default value will be shared between all instances of ArticleButton. So if your type would be list or some other class and you would initialize it in PropertyMetadata to something different then null same instance will be shared as default value
public partial class ArticleButton : UserControl
{
public ArticleButton()
{
InitializeComponent();
}
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register(
"Title",
typeof(String),
typeof(ArticleButton),
new PropertyMetadata("Test", TitlePropertyChanged));
private static void TitlePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as ArticleButton).TitlePropertyChanged(e);
}
private void TitlePropertyChanged(DependencyPropertyChangedEventArgs e)
{
//do something when property changed value
}
public String Title
{
get { return (string)GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
}

Binding IsEnabled on ComboBoxItem does not work

I have this model:
public class Option : BindableBase
{
private bool _enabled;
public bool Enabled
{
get
{
return _enabled;
}
set
{
SetProperty(ref _enabled, value);
}
}
private string _value;
public string Value
{
get
{
return _value;
}
set
{
SetProperty(ref _value, value);
}
}
}
In my viewmodel, I got a list Options of type ObservableCollection<Option>.
I use this XAML piece of code:
<ComboBox Width="200"
ItemsSource="{Binding Options}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Value}"/>
<TextBlock Text="{Binding Enabled}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="IsEnabled" Value="{Binding Enabled}"/>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
I add some Option in my list, some of them have the Enabled property set to true while others do not.
However, the ones having the property to false are still enabled in the ComboBox and I can select them (while I should not!). When using this line of code:
<Setter Property="IsEnabled" Value="false"/>
The options are effectively disabled (I can't select any of them) but I'd like to use some binding. Can someone explain what I'm doing wrong here?
You are setting IsEnabled on the content of your ComboBoxItem while you would need to set it on the ComboBoxItem itself. You would need to set the binding in the ItemContainerStyle. I'm not sure if bindings are supported in style setters though. If they are not, then you might need to use a workaround to set up the binding.
Maybe you need to use path instead. You can try this :
<Setter Property="IsEnabled" Value="{Binding Path=Enabled}"/>

Button Template style when selected

I have a mvvm design pattern set up to do commands and workspaces so that a button is pressed and the proper workspace displays. The buttons have a template binding to pull in the proper image for the template based on the command. I have added another field to the command for a SelectedTemplateResource for when the button is selected out of the array of buttons. The DataTemplate is pulled into the mainwindow to display the buttons.
<DataTemplate x:Key="CommandsTemplate">
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Command="{Binding Path=Command}" Content="{Binding Path=DisplayName}" Template="{Utilities:BindableResource {Binding Path=TemplateResource}}">
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Command}">
<Setter Property="Template">
<Setter.Value>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
Want I want to do is set the value of the setter to the SelectedTemplateResource like so in the Template of the button called TemplateResource. These are all handled through the mainwindowviewmodel to set the appropriate template per button and it works well but I'm not sure how to finish the selected state.
Also, I'm not entirely sure if I should be setting the datatrigger binding to the command path.
Can anybody help me with figuring out how to set the selected state of a button using the design mentioned above?
Cheers.
EDIT
I get the use of the togglebutton trigger however the binding is not working. Heres some more information as for some reason I keep getting key is null error in my app.xaml.cs
The ToggleButton is a datatemplate in a file called MainWindowResources.xaml (ResourceDictionary).
The MainWindow.xaml file pulls in this data template.
I have a bindableresource class to help with the static resource for the resource file.
public class BindableResource : StaticResourceExtension
{
#region Fields
private static readonly DependencyProperty dummyProperty;
#endregion
#region Properties
/// <summary>
/// Gets and sets my binding.
/// </summary>
public Binding MyBinding { get; set; }
#endregion
#region Constructor
/// <summary>
/// Static contruction of the dummy dependency property.
/// </summary>
static BindableResource()
{
dummyProperty = DependencyProperty.RegisterAttached("Dummy", typeof(string), typeof(DependencyObject), new UIPropertyMetadata(null));
}
/// <summary>
/// Constructor.
/// </summary>
public BindableResource()
{
}
/// <summary>
/// Constructor with binding to set.
/// </summary>
/// <param name="binding"></param>
public BindableResource(Binding binding)
{
MyBinding = binding;
}
#endregion
#region External Members
/// <summary>
/// Get the resource key to bind to the resource.
/// </summary>
/// <param name="serviceProvider"></param>
/// <returns></returns>
public override object ProvideValue(IServiceProvider serviceProvider)
{
var target = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
var targetObject = (FrameworkElement)target.TargetObject;
MyBinding.Source = targetObject.DataContext;
var DummyDO = new DependencyObject();
BindingOperations.SetBinding(DummyDO, dummyProperty, MyBinding);
ResourceKey = DummyDO.GetValue(dummyProperty);
return base.ProvideValue(serviceProvider);
}
#endregion
}
However this does not work for the triggers setter value.
<DataTemplate>
<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>
</DataTemplate>
Any thoughts or ideas?
I'm not sure I've understood what you want exactly. You want to change the button's template if it's selected yes?
Firstly a standard button doesn't have a the notion of being "selected". You want a ToggleButton for that. That way you can trigger on its IsChecked property to set your template.
<DataTemplate x:Key="CommandsTemplate">
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<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 DataContext.SelectedTemplateResource}}">
</Trigger>
</Style.Triggers>
</Style>
</ToggleButton.Style>
</ToggleButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
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.

Categories