Making the style's binding more flexible - wpf

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);
}

Related

MVVM WPF Windows app - Binding to custom property in template

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!

Send Value from View Model to UserControl Dependency Property WPF

I have a dependency property in a UserControl with a property called SelectedColor. From my main app, the view of the window that uses this my code is:
<controls:ColorPicker SelectedColor="{Binding MyCanvas.CanvasBackgroundColor}" />
And the code from the view model is:
public MyCanvas { get; set; }
public MyWindowViewModel(MyCanvas myCanvas)
{
MyCanvas = myCanvas;
}
And then the XAML for my UserControl is:
<UserControl . . .>
<Button Click="Button_Click">
<Button.Style>
<Setter Property="Background" Value="Transparent" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="{Binding SelectedColor}" BorderBrush="Black" BorderThickness="1" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Button.Style>
</Button>
</UserControl>
And the code-behind:
public ColorPicker()
{
InitializeComponent();
DataContext = this;
}
public SolidColorBrush SelectedColor
{
get { return (SolidColorBrush)GetValue(SelectedColorProperty); }
set { SetValue(SelectedColorProperty, value); }
}
public static readonly DependencyProperty SelectedColorProperty =
DependencyProperty.Register(nameof(SelectedColor), typeof(SolidColorBrush), new UIPropertyMetadata(null));
I think the problem might be with the line in the code-behind DataContext = this;. Is it correct that declaring this creates an entirely new context for the instance of this user control in the main app and therefore any values sent to it from the view model would be re-initialized? If so, how can I send the value over without it being re-declared? I also need the DataContext = this line because without it some functionality within my UserControl will no longer work.
Has anyone encountered this before?
Thanks in advance!
DataContext = this sets the DataContext of the UserControl to itself. You don't want to do this. Instead you could bind to a property of the UserControl using a {RelativeSource} without setting the DataContext property:
<Border Background="{Binding SelectedColor, RelativeSource={RelativeSource AncestorType=UserControl}}"
BorderBrush="Black" BorderThickness="1" />
Code-behind:
public ColorPicker()
{
InitializeComponent();
}
public SolidColorBrush SelectedColor
{
get { return (SolidColorBrush)GetValue(SelectedColorProperty); }
set { SetValue(SelectedColorProperty, value); }
}
public static readonly DependencyProperty SelectedColorProperty =
DependencyProperty.Register(nameof(SelectedColor), typeof(SolidColorBrush), new UIPropertyMetadata(null));

How to handle attached properties events?

I created an expander style that contains a checkbox in its header. The checkbox state is bound to an attached property:
<Style TargetType="{x:Type Expander}" x:Key="MyCheckboxExpander">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Expander}">
(...)
<CheckBox x:Name="ExpanderHeaderChk" VerticalAlignment="Center" Margin="4,0,0,2"
IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(my:AP.IsChecked)}" />
(...)
I my view, inside the expander I have a stackpanel with a ComboBox.
Whenever the user checks the expander's checkbox, I wan't that the combobox gets the first item selected, on the oher hand whenever the user unchecks it, I wan't that the selecteditem of the combobox be null.
How can I accomplish this? I'm following the MVVM pattern, but since this is more a matter of the view, I'm open to code-behind suggestions.
Well, I think your design is not optimal. You see, you are trying to change the semantics of the Expander. The real expander doesn't have the semantics with additional checkbox, so the control you are creating is not an Expander any more.
I would suggest that you switch to a user control (or maybe a custom control, look at your semantics), and expose the needed event in your control's class. The XAML for the user control should be perhaps an expander with a checkbox.
Edit: example with UserControl (not tested)
(XAML)
<UserControl x:Class="namespace:MyCheckboxExpander">
<Expander>
...
<Checkbox x:Name="cb"/>
...
</Expander>
</UserControl>
(code-behind)
public class MyCheckboxExpander : UserControl
{
MyCheckboxExpander()
{
InitializeComponent();
cb.Check += OnCheck;
}
void OnCheck(object sender, whatever2 args)
{
if (CheckboxTriggered != null)
CheckboxTriggered(new EventArgs<whatever>);
}
public event EventArgs<whatever> CheckboxTriggered;
}
WPF is so powerfull framework, that you can solve you problem just using next style for Expander:
<Style x:Key="myExpanderStyle" TargetType="{x:Type Expander}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Expander}">
<StackPanel>
<CheckBox x:Name="PART_CheckBox" IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" />
<ComboBox x:Name="PART_ComboBox" ItemsSource="{TemplateBinding Content}" />
</StackPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="True">
<Setter TargetName="PART_ComboBox" Property="SelectedIndex" Value="0"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
SAMPLE:
<Expander Style="{StaticResource myExpanderStyle}">
<x:Array Type="sys:String">
<sys:String>1</sys:String>
<sys:String>2</sys:String>
<sys:String>3</sys:String>
</x:Array>
</Expander>
Just XAML! I like XAML declarativity.
But from MVVM perspective, this approach has one disadvantage - I can't cover this case with unit tests. So, I would prefer:
create view model with properties: IsChecked(bound to CheckBox),
SelectedItem(bound to ComboBox) and Source(ItemsSource for ComboBox) -
abstration of my real view without any references on controls;
write a logic in view model that set or unset SelectedItem depending
on IsChecked property;
cover that logic with unit test (yep, you can
even start with this point, if you like test first approach).
I followed the suggestion provided by #Baboon and I created a custom control with a routed event named CheckedChanged, this way I can access it through the view's xaml and code-behind:
[TemplatePart(Name = "PART_Expander", Type = typeof(Expander))]
[TemplatePart(Name = "PART_CheckBox", Type = typeof(CheckBox))]
public class MyCustomExpander : Expander
{
static MyCustomExpander()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyCustomExpander), new FrameworkPropertyMetadata(typeof(MyCustomExpander)));
}
public bool IsChecked
{
get { return (bool)GetValue(IsCheckedProperty); }
set { SetValue(IsCheckedProperty, value); }
}
public static readonly DependencyProperty IsCheckedProperty =
DependencyProperty.Register("IsChecked", typeof(bool), typeof(MyCustomExpander),
new UIPropertyMetadata(false));
#region Events
private CheckBox chkExpander = new CheckBox();
public CheckBox ChkExpander { get { return chkExpander; } private set { chkExpander = value; } }
public static readonly RoutedEvent CheckedChangedEvent = EventManager.RegisterRoutedEvent("ExtraButtonClick",
RoutingStrategy.Bubble,
typeof(RoutedEventHandler),
typeof(MyCustomExpander));
public event RoutedEventHandler CheckedChanged
{
add { AddHandler(CheckedChangedEvent, value); }
remove { RemoveHandler(CheckedChangedEvent, value); }
}
void OnCheckedChanged(object sender, RoutedEventArgs e)
{
RaiseEvent(new RoutedEventArgs(CheckedChangedEvent, this));
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
CheckBox chk = base.GetTemplateChild("PART_CheckBox") as CheckBox;
if (chk != null)
{
chk.Checked += new RoutedEventHandler(OnCheckedChanged);
chk.Unchecked += new RoutedEventHandler(OnCheckedChanged);
}
}
#endregion
}
I want to thank to #Baboon and #Vlad for their help.

How to set a converter in a style but not the path?

I'm trying to create a style for a textbox which I want to be able to use throughout my code. My style defines a converter in the binding of the Text property but does not set its path because my bound data may not be named the same wherever I use this style.
<Style x:Key="CustomTextBox" BasedOn="{StaticResource {x:Type TextBox}}"
TargetType="{x:Type TextBox}">
<Setter Property="Text">
<Setter.Value>
<Binding>
<Binding.Converter>
<CustomTextBoxConverter/>
</Binding.Converter>
</Binding>
</Setter.Value>
</Setter>
</Style>
And then the customTextBox would be use like so :
<TextBox Height="28" Name="txtRate" Style="{StaticResource CustomTextBox}"
MaxLength="5" Text="{Binding Path=BoundData}"/>
When I write the code above, I get an execption that "Two-way binding requires Path or XPath.".
I even tried to create an attached properties that is used in the style binding to reflect this value in the style but I couldn't get working either. See next :
<Converters:SomeConvertingFunction x:Key="CustomTextConverter"/>
<local:CustomAttachedProperties.ReflectedPath x:Key="ReflectedPath"/>
<Style x:Key="CustomTextBox" BasedOn="{StaticResource {x:Type TextBox}}"
TargetType="{x:Type TextBox}">
<Setter Property="Text">
<Setter.Value>
<Binding Path=ReflectedPath Converter=CustomTextConverter/>
</Setter.Value>
</Setter>
</Style>
Used in a page like so :
<TextBox Height="28" Name="txtRate" Style="{StaticResource CustomTextBox}"
MaxLength="5" CustomAttachedProperty="contextBoundDataAsString"/>
The code for the attached property is :
Public Class CustomAttachedProperties
Public Shared ReadOnly ReflectedPathProperty As DependencyProperty =
DependencyProperty.RegisterAttached("ReflectedPath", GetType(String),
GetType(CustomAttachedProperties))
Public Shared Sub SetReflectedPath(element As UIElement, value As String)
element.SetValue(ReflectedPathProperty, value)
End Sub
Public Shared Function GetReflectedPath(element As UIElement) As String
Return TryCast(element.GetValue(ReflectedPathProperty), String)
End Function
End Class
When I try to using the above code it compiles fine but it does not seem to do anything on my XAML, like it might be creating different instances of the CustomAttachedProperty.
Sorry for the lenghty question but I thought it should be easy to create custom controls that have their own default converters with WPF... I'm confused!
You can create a UserControl that does this quite simply:
<UserControl x:Class="namespace.MyCustomConverterTextBox">
<TextBlock Text="{Binding Text, Converter={StaticResource yourconverter}}"/>
</UserControl>
Then declare Text as a DependencyProperty in code-behind:
public partial class MyCustomConverterTextBox : UserControl
{
public string Text {
get{return (string) GetValue(TextProperty);}
set{SetValue(TextProperty, value);}
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(MyCustomConverterBox));
}
This should be enough to let you use it in your xaml:
<local:MyCustomConverterTextBox Text="{Binding YourBinding}" />
I didn't run this code so there might be typos, but it should be enough to give you an idea how to go about this.
The best way to do this in my opinion, is to create a new class that inherits from TextBox, then override the PropertyMetadata for the Text property, allowing yourself an opportunity to change the value in a Coerce callback.

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.

Resources