Dependency Property not set from style - c#

I'm trying to synchronize scrolling between two datagrids so that each scroll is mirrored between them (either horizontal or vertical scrolling), after googling around how to do I started to implement my method but the setter call from my scrollbar style never calls the dependency object to set the value.
This is my data grid.
<dataGridEx:DataGridEx ColumnHeaders="{Binding SystemMonitorValues.ColumnHeaders}"
ItemsSource="{Binding SystemMonitorValues.Rows}"
Style="{StaticResource DataGridStyle}"
ScrollViewer.CanContentScroll="True"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Auto">
<dataGridEx:DataGridEx.Resources>
<Style TargetType="{x:Type ScrollBar}">
<Setter Property="Background" Value="Red"/>
<Setter Property="scroll:ScrollSynchronizer.ScrollGroup" Value="Group1" />
</Style>
</dataGridEx:DataGridEx.Resources>
</dataGridEx:DataGridEx>
So within the scroll bar style I am attempting to set the ScrollSynchronizer.ScrollGroup to have the value "Group1".
My ScrollSynchronizer is setup as follows:
public class ScrollSynchronizer : DependencyObject
{
public static readonly DependencyProperty ScrollGroupProperty = DependencyProperty.Register(#"ScrollGroup",
typeof(string), typeof(ScrollSynchronizer), new PropertyMetadata(new PropertyChangedCallback(OnScrollGroupChanged)));
static ScrollSynchronizer()
{
}
public string ScrollGroup
{
get
{
return (string)this.GetValue(ScrollGroupProperty);
}
set
{
this.SetValue(ScrollGroupProperty, value);
}
}
private static void OnScrollGroupChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var scrollViewer = d as System.Windows.Controls.ScrollViewer;
...
}
I'm placing a breakpoint within the OnScrollGroupChanged method which is the PropertyChangedCallback for the DependencyProperty but for some reason this is never hit.
I know the style is working as the background of the scrollbar is being set to Red but the setter for the ScrollGroup doesn't seem to want to be called, this is also shown in Snoop in that the Style is correctly set with the two setters and even the setter for the ScrollSynchronizer point's to the correct object.
I'm simply at a loss to why this is not being set.

ScrollSynchronizer.ScrollGroup should be an attached property instead of a regular dependency property:
public static class ScrollSynchronizer
{
public static readonly DependencyProperty ScrollGroupProperty =
DependencyProperty.RegisterAttached(
"ScrollGroup", typeof(string), typeof(ScrollSynchronizer),
new PropertyMetadata(OnScrollGroupChanged));
public static string GetScrollGroup(DependencyObject obj)
{
return (string)obj.GetValue(ScrollGroupProperty);
}
public static void SetScrollGroup(DependencyObject obj, string value)
{
obj.SetValue(ScrollGroupProperty, value);
}
private static void OnScrollGroupChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var scrollBar = d as ScrollBar;
...
}
}
Note also that the DependencyObject parameter of the PropertyChangedCallback is of type ScrollBar when you set the property in a ScrollBar Style.

Related

Use behavior in style

I have a behavior ,How can I use it in TextBlock Style so that it gets applied to all TextBlocks.
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Distance}" >
<i:Interaction.Behaviors>
<Behaviors:EmptyToNaBehaviour/>
</i:Interaction.Behaviors>
</TextBlock>
this is my attached behavior which is basically changing the empty value to N/A
public class EmptyToNaBehaviour : Behavior<TextBlock>
{
protected override void OnAttached()
{
var txtblock = this.AssociatedObject as TextBlock;
if (txtblock != null && string.IsNullOrEmpty(txtblock.Text))
{
txtblock.Text = "N/A";
}
}
}
There are basically two different ways of implementing behaviours in WPF, commonly referred to as attached behaviours and Blend behaviours.
An attached behaviour is simply an attached property with a PropertyChangedCallback change handler attached to it that performs some action on or extends the DependencyObject to which it is attached when the value of the dependency property changes.
You typically define an attached behaviour as a static class with a set of static methods that get and set the value of the dependency property and perform the desired logic as a result of the callback being invoked. You could easily set the value of any such properties in a Style:
<Setter Property="local:EmptyToNaBehaviour.SomeProperty" Value="x"/>
A Blend behaviour provides a better way of encapsulating the functionality of a behaviour compared to an ordinary attached behaviour. They are also more design friendly easily as they can be easily attached to visual elements in the UI via drag-drop functionality in Blend. But I am afraid you cannot really add a Blend behaviour to a Style setter:
How to add a Blend Behavior in a Style Setter
So if you want to be able to apply your behaviour in the context of a Style, you should write an attached behaviour instead of a Blend behaviour.
Edit: Your behaviour is a Blend behaviour. Replace it by an attached behaviour:
public class EmptyToNaBehaviour
{
public static string GetText(TextBlock textBlock)
{
return (string)textBlock.GetValue(IsBroughtIntoViewWhenSelectedProperty);
}
public static void SetText(TextBlock textBlock, string value)
{
textBlock.SetValue(IsBroughtIntoViewWhenSelectedProperty, value);
}
public static readonly DependencyProperty IsBroughtIntoViewWhenSelectedProperty =
DependencyProperty.RegisterAttached(
"Text",
typeof(string),
typeof(EmptyToNaBehaviour),
new UIPropertyMetadata(null, OnTextChanged));
private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextBlock txtblock = d as TextBlock;
if(txtblock != null)
txtblock.Loaded += Txtblock_Loaded;
}
private static void Txtblock_Loaded(object sender, RoutedEventArgs e)
{
TextBlock txtblock = sender as TextBlock;
string text = GetText(txtblock);
if (txtblock != null && !string.IsNullOrEmpty(text) && string.IsNullOrEmpty(txtblock.Text))
{
txtblock.Text = text;
}
}
}
Usage:
<TextBlock Grid.Row="0" Grid.Column="1" Text="text"
Behaviors:EmptyToNaBehaviour.Text="N/A">
</TextBlock>
Or in a Style:
<Style x:Key="style" TargetType="TextBlock">
<Setter Property="Behaviors:EmptyToNaBehaviour.Text" Value="N/A" />
</Style>

Dependency Property not set from ViewModel

I create a custom control "CustomAutoCompleteBox" (which inherit of AutoCompleteBox) with one dependency property "CurrentItem".
public static readonly DependencyProperty CurrentItemProperty =
DependencyProperty.Register("CurrentItem", typeof(CityEntity), typeof(CustomAutoCompleteBox),
new FrameworkPropertyMetadata(
null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public CityEntity CurrentItem
{
get { return (CityEntity)GetValue(CurrentItemProperty); }
set { SetValue(CurrentItemProperty, value); }
}
This custom control have also a property "InternalCurrentItem".
public CityEntity InternalCurrentItem
{
get { return _internalCurrentCity; }
set
{
if (_internalCurrentCity == value) return;
_internalCurrentCity = value;
OnPropertyChanged();
CurrentItem = value;
}
}
The DataContext is define to himself in the constructor :
public VilleAutoCompleteBox()
{
DataContext = this;
...
}
And the Style set ItemsSource and SelectedItem like this:
<Style TargetType="{x:Type infrastructure_controls:CustomAutoCompleteBox}" BasedOn="{StaticResource AutoCompleteBoxFormStyle}">
<Setter Property="ItemsSource" Value="{Binding InternalItems, Mode=OneWay}" />
<Setter Property="SelectedItem" Value="{Binding InternalCurrentItem, Mode=TwoWay}" />
...
</Style>
In summary, ItemsSource is bind to internal property "InternalItems" and SelectedItem is bind to internal property "InternalCurrentItem".
For use it, I declare this CustomAutoCompleteBox like this :
<infrastructure_usercontrols:CustomAutoCompleteBox Width="200" CurrentItem="{Binding DataContext.VmCurrentItem, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Mode=TwoWay}" />
I have bind the dependency property "CurrentItem" to the ViewModel's property "VmCurrentItem".
Everything works fine except for one thing.
When I type text in the control, the InternalCurrentItem property changes correctly. Same for the CurrentItem property in my ViewModel.
Concretely, InternalCurrentItem is correctly modified (Set). This property sets the CurrentItem dependency property, and this dependency property sets VmCurrentItem.
The opposite is not true. If I change directly the value of the VmCurrentItem property in the ViewModel, the CurrentItem property is not changed. I do not understand why.
The first case causes the following chain of events:
SelectedItem is changed
InternalCurrentItem is updated by the framework due to the binding
You manually update CurrentItem in the InternalCurrentItem setter
VmCurrentItem is updated by the framework due to the binding
In the opposite direction this is what happens:
VmCurrentItem is changed
CurrentItem is updated by the framework due to the binding
...and that's it. There's no binding and no piece of code that would update InternalCurrentItem when CurrentItem changes. So what you need to do is to register a PropertyChangedCallback for your CurrentItemProperty which will update InternalCurrentItem:
public static readonly DependencyProperty CurrentItemProperty =
DependencyProperty.Register(
"CurrentItem",
typeof(CityEntity),
typeof(CustomAutoCompleteBox),
new FrameworkPropertyMetadata
{
BindsTwoWayByDefault = true,
PropertyChangedCallback = CurrentItemPropertyChanged
});
private static void CurrentItemPropertyChanged(
DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = (CustomAutoCompleteBox)d;
control.InternalCurrentItem = (CityEntity)e.NewValue;
}
You need to declare the property the same way as the first:
public static readonly DependencyProperty InternalCurrentItemProperty =
DependencyProperty.Register("InternalCurrentItem", typeof(CityEntity), typeof(CustomAutoCompleteBox),
new FrameworkPropertyMetadata(
null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public CityEntity InternalCurrentItem
{
get{ return (CityEntity)GetValue(InternalCurrentItemProperty); }
set
{
SetValue(InternalCurrentItemProperty, value);
}
}

Style vs inline property setting in silverlight with a custom control

I have a custom silverlight control that is pretty much a glorified text box. I have some properties in it I need to be able to set from the XAML in silverlight, so for each property I have created something like this:
public bool UseCustomTooltips
{
get { return _useCustomTooltips; }
set
{
_useCustomTooltips = value;
DoSomeOtherStuff();
}
}
public static readonly DependencyProperty UseCustomTooltipsProperty = DependencyProperty.Register("UseCustomTooltips",
typeof(bool), typeof(MyControl), new PropertyMetadata(false, PropertyChangedCallback));
In the XAML I can create the control and specify a value for that property like this:
<Controls:MyControl UseCustomTooltips="True" />
The control's state is update, my callback is hit, and all is as it should be. However, when I try to specify that state from a style instead of in each instance of the control like this:
<Style x:Key="MyControl" TargetType="Controls:MyControl">
<Setter Property="Foreground" Value="Yellow"/>
<Setter Property="UseCustomTooltips" Value="True"/>
</Style>
<Controls:MyControl Style="{StaticResource MyControl}" />
my custom properties are never set and my callback is never hit even though the inherited property Foreground takes on the value specified in the style. This leads me to assume there is something wrong with the way my dependency property is wired up but everything I've seen so far online tells me I have it correct.
Can anyone point me in the right direction?
Thanks.
That is not the correct way to register a dependency property.
public bool UseCustomTooltips
{
get { return (bool)GetValue(UseCustomTooltipsProperty); }
set { SetValue(UseCustomTooltipsProperty, value); }
}
public static readonly DependencyProperty UseCustomTooltipsProperty =
DependencyProperty.Register("UseCustomTooltips",
typeof(bool),
typeof(MyControl),
new PropertyMetadata(false, new PropertyChangedCallback(MyCallbackMethod)));
Use the propdp snippet, it really is a beautiful thing.

Any better way of adding number to ListView's ItemContainerStyle?

I'm trying to extend my ListView's ItemContainerStyle a little and add a TextBlock with binding to a property. It should show ListView.SelectedItems.Count.
For now I've one working solution, but I'm not happy with it (I suspect that there is much easier way and probably more clean). It goes like this:
<Style x:Key="MyItemStyle" TargetType="ListViewItem">
<!--Some code-->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListViewItem">
<!--Some code-->
<TextBlock DataContext="{Binding ElementName=contentPresenter, Path=DataContext}" Text="{Binding Number}" Foreground="Red"/>
The idea is very simple - I set the DataContext the same as contentPresenter's, which means that if I've in my ItemClass a property Number and I put there Item.Number = myList.SelectedItems.Count; everything works fine.
But is there other way to do it in this Style? Without additional property in my ItemClass? Somehow maybe extend ListView or ListViewItem?
Initially I thought I could use ElementName binding to retrieve the ListView, and then bind the Text of your TextBlock to the ListView's SelectedItems.Count. Something like the following -
<!-- this won't work -->
<TextBlock Text="{Binding Path=SelectedItems, ElementName=myList, Converter="{StaticResource GetCountConverter}"}" />
However, unlike the SelectedItem dependency property, this wouldn't work because SelectedItems is merely a normal read-only property.
A common workaround would be to create a static helper class with a couple of attached properties. Something like this -
public static class ListViewEx
{
public static int GetSelectedItemsCount(DependencyObject obj)
{
return (int)obj.GetValue(SelectedItemsCountProperty);
}
public static void SetSelectedItemsCount(DependencyObject obj, int value)
{
obj.SetValue(SelectedItemsCountProperty, value);
}
public static readonly DependencyProperty SelectedItemsCountProperty =
DependencyProperty.RegisterAttached("SelectedItemsCount", typeof(int), typeof(ListViewEx), new PropertyMetadata(0));
public static bool GetAttachListView(DependencyObject obj)
{
return (bool)obj.GetValue(AttachListViewProperty);
}
public static void SetAttachListView(DependencyObject obj, bool value)
{
obj.SetValue(AttachListViewProperty, value);
}
public static readonly DependencyProperty AttachListViewProperty =
DependencyProperty.RegisterAttached("AttachListView", typeof(bool), typeof(ListViewEx), new PropertyMetadata(false, Callback));
private static void Callback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue)
{
var listView = d as ListView;
if (listView == null) return;
listView.SelectionChanged += (s, args) =>
{
SetSelectedItemsCount(listView, listView.SelectedItems.Count);
};
}
}
Basically here I've created a SelectedItemsCount attached property to leverage data binding. Whenever the SelectionChanged is fired, the code updates the attached property to the Count of the SelectedItems so they are always in sync.
Then in the xaml, you will need to first attach the helper to the ListView (in order to retrieve the ListView instance and subscribe to its SelectionChanged event),
<ListView x:Name="myList" local:ListViewEx.AttachListView="true"
and lastly, update the binding in the TextBlock xaml.
<TextBlock Text="{Binding Path=(local:ListViewEx.SelectedItemsCount), ElementName=myList}" />

Make dependency object properties bindable as a static resource?

How to make an array of dependency object properties bindable for later binding as a static resource?
The code I have now, it seems that my DependencyObject bypasses the dependency property system...
I have the following class:
public class ValueMarker : DependencyObject
{
public static readonly DependencyProperty BrushProperty = DependencyProperty.Register("Brush", typeof(Brush), typeof(ValueMarker), new FrameworkPropertyMetadata(Brushes.Aqua));
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(double), typeof(ValueMarker), new FrameworkPropertyMetadata(0d));
public static readonly DependencyProperty OffsetProperty = DependencyProperty.Register("Offset", typeof(double), typeof(ValueMarker), new FrameworkPropertyMetadata(0d));
public Brush Brush
{
get { return (Brush)GetValue(BrushProperty); }
set { SetValue(BrushProperty, value); }
}
public double Offset
{
get { return (double)GetValue(OffsetProperty); }
set { SetValue(OffsetProperty, value); }
}
public double Value
{
get { return (double)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
}
In the XAML, I create a resource array of these with some bindings like so:
<x:Array Type="my:ValueMarker" x:Key="plainMarks">
<my:ValueMarker Brush="Red" Offset="-5" Value="{Binding Path=...}" />
<my:ValueMarker Brush="Orange" Offset="-5" Value="{Binding Path=...}"/>
<my:ValueMarker Brush="Orange" Offset="-5" Value="{Binding Path=...}"/>
<my:ValueMarker Brush="Red" Offset="-5" Value="{Binding Path=...}" />
</x:Array>
While debugging the bindings, I've noticed that should I remove the setter for the DP, the XAML would display an error saying the property is missing. It was my understanding that XAML uses DP system to assign value thus enabling binding. In this case, if the XAML expect a 'normal' property, binding is impossible. Anyone can enlighten me on how can I make it work?
The reason you cannot bind your ValueMarkers here is because:
1.They are not in the VisualTree of your window/usercontrol.
2.They are not object of Type that can inherit DataContext even if they are not part of Visual Tree.
So in order to make your ValueMarkers bind to the properties in the View DataContext, first of all you will have to derive them from Freezable class like below:
public class ValueMarker : Freezable
{
//All your Dependency Properties comes here//
protected override Freezable CreateInstanceCore()
{
return new ValueMarker();
}
}
After doing this you can simply bind your object like below:
<my:ValueMarker x:Key="vm1" Brush="Orange" Offset="-5" Value="{Binding Path=Text1}"/>
Here Text1 is property in Windows/usercontrols DataContext
Then you can use this resource as:
<TextBox Text="{Binding Value, Source={StaticResource vm1}, StringFormat=F2}"/>
Similarly you can create resource for other ValueMarkers to use them in binding.
You will not be able to bind by creating the x:Array as simply x:Array not lies in visualtree and does not inherit DataContext hence its elements also have no access to it.
If you still want to use the collection whose element should support binding, then you will need to create your own collection class that should inherit Freezable and exposes DependancyProperty to capture the DataContext and set it on child elements also.

Categories