MVVM WPF Windows app - Binding to custom property in template - wpf

I'm having a binding problem, and I'm starting to think that there's something wrong with my approach to this problem.
I'm inside a Button style template, and I need to bind a DataTrigger to a custom property "BindingRef".
This code should change an Image named btnImage to "DisabledImage" when the custom property changes value.
Here's the code in the template:
<DataTrigger Binding="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(helper:BindingHelper.BindingRef), UpdateSourceTrigger=PropertyChanged}" Value="{x:Static helper:StatusEnum.Disabled}">
<Setter TargetName="btnImage" Property="Source" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=Button}, Path=(helper:ImageLoader.DisabledImage)}" />
</DataTrigger>
I know, a lot of things happening there.
However everything works if instead of "(helper:BindingHelper.BindingRef)" I set a fixed binding source.
Here's BindingHelper:
public static class BindingHelper
{
public static string GetBindingRef(DependencyObject target)
{
return (string)target.GetValue(BindingRefProperty);
}
public static void SetBindingRef(DependencyObject target, string value)
{
target.SetValue(BindingRefProperty,value);
}
public static readonly DependencyProperty BindingRefProperty =
DependencyProperty.RegisterAttached("BindingRef",
typeof(string),
typeof(BindingHelper),
new PropertyMetadata(null));
}
and inside the View:
<Button ...
helper:ImageLoader.DisabledImage="/Resources/disabled.png"
Style="{StaticResource btnStyle}"
Command="{Binding btnCommand}"
helper:BindingHelper.BindingRef="MyProperty" />
This compiles and I get no errors, but it's not working.
Any idea?
Thanks!

Related

Changing dependency property on behavior

I Have a Behavior that I created a dependency property on it, this behavior is used in an item template of a ListBox.
What I need to do is change the value in the dependency property when the ListBoxItem is selected.
I tried to give the Behavior a name in the Xaml but the trigger didn't recognize it (compilation error).
I tried creating a 0 sized grid, changing the color of the background of that grid in the trigger and binding the dependency property to the background of that grid, it took the first value but didn't update when the trigger changed the background.
Behavior class:
public class DragToCenter : Behavior<FrameworkElement>
{
public static readonly DependencyProperty CenteredTextColorProperty = DependencyProperty.Register(
"CenteredTextColor",
typeof(Color),
typeof(DragToCenter),
new UIPropertyMetadata(Brushes.Black.Color, CenteredTextColorPropertyChanged));
private static void CenteredTextColorPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DragToCenter lBase = d as DragToCenter;
if (lBase != null)
{
d.SetValue(CenteredTextColorProperty, e.NewValue);
lBase.DrowImage();
}
}
public Color CenteredTextColor
{
get
{
return (Color)GetValue(CenteredTextColorProperty);
}
set
{
SetValue(CenteredTextColorProperty, value);
}
}
protected override void OnAttached()
{
// Do something
}
private void DrowImage()
{
// Do something that changes the view so it will be visible if this is triggered
}
XAML:
<ListBox ItemsSource="{Binding DisplayValues}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid x:Name="CenterText" Height="0" Width="0" Background="Gray"/>
<Border>
<i:Interaction.Behaviors>
<b:DragToCenter CenteredTextColor="{Binding ElementName=CenterText, Path=Background.Color}"/>
</i:Interaction.Behaviors>
</Border>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}}, Path=IsSelected}" Value="True">
<Setter TargetName="CenterText" Property="Background" Value="White"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Edit:
The idea is to change the behavior functionality (a small change) when the row is selected in the ListBox

Change style of TextBlock for TargetNullValue

I want to change the Style of a TextBlock if the value of the bound property is null. I have specified a value for TargetNullValue of the TextBlock to be displayed, but i want to display it with an alternativ style. How can i do it.
My current solution is to use two TextBlocks and control the Visibility of both, to toggle between original and alternativ style. But this solution is not viable, cause i need to duplicate each TextBlock, for displaying alternativ version.
Current Solution:
<TextBlock Visibility="{Binding MyText, Converter={StaticResource nullToVisibilityConverter}}"
FontSize="20"
Foreground="Black"
Text="{Binding MyText}" />
<TextBlock Visibility="{Binding MyText, Converter={StaticResource nullToVisibilityConverter}}"
FontSize="20"
FontStyle="Italic"
Foreground="Gray"
Text="None" />
Needed Solution:
<TextBlock FontSize="20"
Foreground="Black"
Text="{Binding MyText, TargetNullValue='None'}" />
<!-- plus any styles, templates or triggers, to change style of TextBlock for TargetNullValue -->
How can i use an alternativ style for a TargetNullValue. Any solutions using Styles, Triggers or Templates are welcome.
Note: this is for WPF, you might have to convert anything that doesn't quite mesh with SL5.0.
The easiest solution (unless this requirement is ubiquitous) is to make a specific style for each instance which binds to the same property as the textblock, checks for Null and sets properties there.
This example will paste nicely into Kaxaml.
<Style x:Key="tacoStyle" TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding Source={StaticResource taco}}" Value="{x:Null}">
<Setter Property="Foreground" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Resources>
<TextBlock Style="{StaticResource tacoStyle}" Text="{Binding Source={StaticResource taco}, TargetNullValue='bacon'}"/>
Of course if you need a more generic solution, you could extend TextBlock and add some logic to handle this.
<StackPanel>
<StackPanel.Resources>
<x:NullExtension x:Key="taco"/>
<Style x:Key="AltTacoStyle" TargetType="yourNS:ExtendedTextBlock">
<Setter Property="Foreground" Value="Pink"/>
</Style>
</StackPanel.Resources>
<yourNS:ExtendedTextBlock Text="{Binding Source={StaticResource taco}, TargetNullValue='bacon'}"
AltStyle="{StaticResource AltTacoStyle}"
AllowAltStyleOnNull="True"/>
</StackPanel>
And the control:
public class ExtendedTextBlock : TextBlock {
public static readonly DependencyProperty AllowAltStyleOnNullProperty =
DependencyProperty.Register("AllowAltStyleOnNull", typeof (bool), typeof (ExtendedTextBlock), new PropertyMetadata(default(bool)));
public bool AllowAltStyleOnNull {
get { return (bool) GetValue(AllowAltStyleOnNullProperty); }
set { SetValue(AllowAltStyleOnNullProperty, value); }
}
public static readonly DependencyProperty AltStyleProperty =
DependencyProperty.Register("AltStyle", typeof (Style), typeof (ExtendedTextBlock), new PropertyMetadata(default(Style)));
public Style AltStyle {
get { return (Style) GetValue(AltStyleProperty); }
set { SetValue(AltStyleProperty, value); }
}
static ExtendedTextBlock() {
TextProperty.OverrideMetadata(typeof(ExtendedTextBlock), new FrameworkPropertyMetadata(default(string), PropertyChangedCallback));
}
private static void PropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs) {
var tb = (ExtendedTextBlock)dependencyObject;
var binding = tb.GetBindingExpression(TextProperty);
if (binding != null && binding.DataItem == null) {
if (tb.AllowAltStyleOnNull)
tb.Style = tb.AltStyle;
}
}
}
You'll need to flesh that out a bit to be production-ready, but you get the idea.

Access ComboBoxItem DisplayValue

I have a comboBox that has an ItemsSource of a list of objects. So the DisplayMemberPath is set to a particular property of the object. Of course this means that the correct value is displayed in the ComboBoxItem.
My issue is that I would like to be able to get that "Value" that is returned by the DisplayMemberPath in XAML so that I can bind it to something else. i.e. I would like to have a "DisplayText" property on the ComboBoxItem.
Of course I don't have this, so, does anyone know of a way to get this value without traversing down into the template of the ComboBoxItem looking for the ContentHost?
If you're interested in my specific use of this, I'm trying to do this on the style of the ComboBox:
....
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style>
<Setter
Property="AutomationProperties.AutomationId"
Value="{Binding RelativeSource={RelativeSource Self}, Path=MagicPathForDisplayedText}"/>
....
Of course Path=Content works just fine if you're just binding your ItemsSource to properties, but when it's an Object with a DisplayMemberPath, Content will be that Object.
Thanks for any help or re-framing of the problem.
The easiest way to handle problems like this is usually Attached Properties and Behaviors.
You could create two attached properties called DisplayMemberPath and DisplayText, then you bind DisplayMemberPath to the parent ComboBox DisplayMemberPath and in the PropertyChangedCallback you set up a binding of your own with the same path for DisplayText. After that you have a property which you can bind to
<Style x:Key="ComboBoxStyle" TargetType="ComboBox">
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="ComboBoxItem">
<Setter Property="behaviors:DisplayTextBehavior.DisplayMemberPath"
Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type ComboBox}},
Path=DisplayMemberPath}"/>
<Setter Property="AutomationProperties.AutomationId"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(behaviors:DisplayTextBehavior.DisplayText)}"/>
</Style>
</Setter.Value>
</Setter>
</Style>
DisplayTextBehavior
public class DisplayTextBehavior
{
public static DependencyProperty DisplayMemberPathProperty =
DependencyProperty.RegisterAttached("DisplayMemberPath",
typeof(string),
typeof(DisplayTextBehavior),
new FrameworkPropertyMetadata("", DisplayMemberPathChanged));
public static string GetDisplayMemberPath(DependencyObject obj)
{
return (string)obj.GetValue(DisplayMemberPathProperty);
}
public static void SetDisplayMemberPath(DependencyObject obj, string value)
{
obj.SetValue(DisplayMemberPathProperty, value);
}
private static void DisplayMemberPathChanged(object sender, DependencyPropertyChangedEventArgs e)
{
ComboBoxItem comboBoxItem = sender as ComboBoxItem;
string displayMemberPath = GetDisplayMemberPath(comboBoxItem);
comboBoxItem.SetBinding(DisplayTextProperty, new Binding(displayMemberPath));
}
public static DependencyProperty DisplayTextProperty =
DependencyProperty.RegisterAttached("DisplayText",
typeof(string),
typeof(DisplayTextBehavior),
new FrameworkPropertyMetadata(""));
public static string GetDisplayText(DependencyObject obj)
{
return (string)obj.GetValue(DisplayTextProperty);
}
public static void SetDisplayText(DependencyObject obj, string value)
{
obj.SetValue(DisplayTextProperty, value);
}
}
You could bind an intermediate Object in the ViewModel to the SelectedItem property on the Combobox. Then also Bind your other display item to that intermediate object. Then when the PropertyChanged event is fired by selecting an item, the display will also update via the event chain.
Are you using SelectedValuePath? If not you could could set it the same as DisplayMemberPath and then the selected value is available as SelectedValue.

DataTrigger, Binding to nested properties via TemplatedParent

According to msdn, it should be perfectly legal, and possible, to bind something to a nested property:
<Binding Path="propertyName.propertyName2" .../>
<Binding Path="propertyName.propertyName2.propertyName3" .../>
In my case, it's not so, though...
I have a custom control, MyControl, with a dependency property ViewModel:
public static DependencyProperty ViewModelProperty = DependencyProperty.Register(
"ViewModel", typeof(IViewModel), typeof(MyControl));
public IViewModel ViewModel
{
get { return (IViewModel)GetValue(ViewModelProperty); }
set { SetValue(ViewModelProperty, value); }
}
and in the control template, I try to bind to properties in that viewmodel:
<Style TargetType="{x:Type my:MyControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type my:MyControl}">
<Grid>
<TextBox Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=ViewModel.Text}"/>
<Button x:Name="MyButton" Content="Visible by trigger" Visibility="Collapsed" />
</Grid>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=ViewModel.ButtonVisible}" Value="True">
<Setter TargetName="MyButton" Property="Visibility" Value="Visible" />
</DataTrigger>
.../>
In the viewmodel itself, I have a preoperty Text as follow:
public string Text
{
get { return m_text; }
set
{
m_text = value;
OnPropertyChanged("Text");
}
}
public bool ButtonVisible
{
get { return m_buttonVisible; }
set
{
m_buttonVisible = value;
OnPropertyChanged("ButtonVisible"); }
}
I get no bind errors, but things doesn't happend...
Any clues?
Edit
It looks like the bindings work half way. When the text is changed in the editbox, my Text property is set, but if the Text-property is set in code, the ui won't update.
Edit 2
Looks like my first attempt at simplifying the case before posting was a little to successful... As #Erno points out, the code that I posted seems to work OK.
I have looked at the original code some more, and added a trigger to the scenario. The original code uses triggers to show parts of the ui at given conditions. These are also binded to nested properties. I now think that these triggers fail to trigger. I have updated the code. If it still doesn't show whats wrong, I can post a sample application some where.
There is a comma missing:
<TextBox Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=ViewModel.Text}"/>
EDIT
Add Mode=TwoWay to the binding:
<TextBox Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=ViewModel.Text, Mode=TwoWay}"/>
EDIT2
Got it! I could reproduce and fix it.
Replace the TemplatedParent with Self in the binding.
Read this explanation

Making the style's binding more flexible

I'm trying to make the existing style more flexible.
I'm attaching a dep. property to the double click event of the data grid row control.
On double-clicking the row i want a certain window to open.
To achieve this I've implemented a dep. property and added a style definition.
Below is the part from the view file:
<Style x:Key="SomeKey" TargetType={x:Type DataGridRow} BasedOn= "SomeOtherKey">
<Setter Property="vm:DataGridBehavior:OnDoubleClick" Value="{Binding Path=CommandName,
RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}">
</Setter>
</Style>
the attached property hooks to the double-click event on the data row
and executes the relay command bound to the view.
I'd like to enable more flexibility in the style
by specifying the command-to-be-invoked name in the view itself (as it may differ between different views).
How can I achieve that?
Is there any template definition that I'm missing?
Any help would be greatly appreciated :)
You could subclass DataGrid and add a Property for the Command, e.g MyCommand. Then you could bind that ICommand in each DataGrid and use MyCommand as Path in the RowStyle Binding
DataGrids
<local:CommandDataGrid RowStyle="{StaticResource SomeKey}"
MyCommand="{Binding CommandName1}">
<!-- ... -->
<local:CommandDataGrid RowStyle="{StaticResource SomeKey}"
MyCommand="{Binding CommandName2}">
RowStyle
<Style x:Key="SomeKey" TargetType="{x:Type DataGridRow}">
<Setter Property="vm:DataGridBehavior.OnDoubleClick"
Value="{Binding Path=MyCommand,
RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"/>
</Style>
CommandDataGrid
public class CommandDataGrid : DataGrid
{
public static readonly DependencyProperty MyCommandProperty =
DependencyProperty.Register("MyCommand",
typeof(ICommand),
typeof(CommandDataGrid),
new UIPropertyMetadata(null));
public ICommand MyCommand
{
get { return (ICommand)GetValue(MyCommandProperty); }
set { SetValue(MyCommandProperty, value); }
}
}
Alternatively, you could create an attached property e.g MyCommand that you use
DataGrid
<DataGrid vm:DataGridBehavior.MyCommand="{Binding CommandName}"
RowStyle
<Style x:Key="SomeKey" TargetType="{x:Type DataGridRow}">
<Setter Property="vm:DataGridBehavior.OnDoubleClick"
Value="{Binding Path=(vm:DataGridBehavior.MyCommand),
RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"/>
</Style>
MyCommandProperty
public static readonly DependencyProperty MyCommandProperty =
DependencyProperty.RegisterAttached("MyCommand",
typeof(ICommand),
typeof(DataGridBehavior),
new UIPropertyMetadata(null));
public static void SetMyCommand(DependencyObject element, ICommand value)
{
element.SetValue(MyCommandProperty, value);
}
public static ICommand GetMyCommand(DependencyObject element)
{
return (ICommand)element.GetValue(MyCommandProperty);
}

Resources