Use behavior in style - c#

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>

Related

WPF - How to apply converter to all DataGridTextColumn?

I would like using WPF Apply Converter to Binding Value to all DataGridTextColumn in application.
For single DataGridTextColumn converter working fine:
<DataGridTextColumn
Header ="Value"
Binding="{Binding Value, Converter={StaticResource decimalConverter}}"
/>
But in application I got many (over 100) DataGridTextColumn in different DataGrid's and I would know best solution for that instead of applying for each columns converter separately.
I know using Style there is possibility to modify some property for all Type of controls (e.g. foreground) but not sure how use these for the Binding Value and Converter?
You can do it with the help of global style and attached property. You cannot create global style (or any style) for DataGridTextColumn because it does not inherit from FrameworkElement. But you can create style for DataGrid itself, set attached property for grid in that style, and in property changed handler of that attached property set converter for all column bindings when they are added. Sample code:
public class DataGridHelper : DependencyObject {
public static IValueConverter GetConverter(DependencyObject obj) {
return (IValueConverter) obj.GetValue(ConverterProperty);
}
public static void SetConverter(DependencyObject obj, IValueConverter value) {
obj.SetValue(ConverterProperty, value);
}
public static readonly DependencyProperty ConverterProperty =
DependencyProperty.RegisterAttached("Converter", typeof(IValueConverter), typeof(DataGridHelper), new PropertyMetadata(null, OnConverterChanged));
private static void OnConverterChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
// here we have our converter
var converter = (IValueConverter) e.NewValue;
// first modify binding of all existing columns if any
foreach (var column in ((DataGrid) d).Columns.OfType<DataGridTextColumn>()) {
if (column.Binding != null && column.Binding is Binding)
{
((Binding)column.Binding).Converter = converter;
}
}
// then subscribe to columns changed event and modify binding of all added columns
((DataGrid) d).Columns.CollectionChanged += (sender, args) => {
if (args.NewItems != null) {
foreach (var column in args.NewItems.OfType<DataGridTextColumn>()) {
if (column.Binding != null && column.Binding is Binding) {
((Binding) column.Binding).Converter = converter;
}
}
}
};
}
}
Then create global style somewhere (like App.xaml):
<Application.Resources>
<local:TestConverter x:Key="decimalConverter" />
<Style TargetType="DataGrid">
<Setter Property="local:DataGridHelper.Converter"
Value="{StaticResource decimalConverter}" />
</Style>
</Application.Resources>

Dependency Property not set from style

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.

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}" />

Focus on a textbox in autocompletebox?

this is my xaml:
<toolkit:AutoCompleteBox Name="signalNameEditor"
ItemsSource="{Binding MySource}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
IsTextCompletionEnabled="True"
FilterMode="StartsWith"
ValueMemberPath="Label"
MinimumPrefixLength="3"
MinimumPopulateDelay="800"
Style="{StaticResource autoCompleteBoxStyle}">
<toolkit:AutoCompleteBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Name="textBlock" Text="{Binding Label}"/>
</StackPanel>
</DataTemplate>
</toolkit:AutoCompleteBox.ItemTemplate>
</toolkit:AutoCompleteBox>
So, how could i get textblock element in my view? I tried this:
var textBlock = signalNameEditor.FindName("textBlock");
but it is wrong. So could you help me with this or redirect me to a proper solution. Thanks in advance.
Thanks for all aswers, that worked
var textBlock = ((StackPanel)signalNameEditor.ItemTemplate.LoadContent()).FindName("textBlock") as TextBlock;
but unfortunately I didn't get the result, that I expected. The question is how to get focus on textbox in autocompletebox, so that when focus is on autocompletebox I could write something there without double clicking.
I thought that I could do something inside my view
public void SetFocus
{
var textBlock = ((StackPanel)signalNameEditor
.ItemTemplate
.LoadContent())
.FindName("textBlock") as TextBlock;
textBlock.Focus();
}
I know that there are a lot of howto examples for setting focus like this one
autocompletebox focus in wpf
but I can't make it work for me. Is there a solution, that I could get without writing AutoCompleteFocusableBox class?
The solution was simplier. Actually i need to set focus on a textbox in a autocompletebox. For this purpose I used style defined as a regular style http://msdn.microsoft.com/ru-ru/library/dd728668(v=vs.95).aspx
After it in my view I could use the following:
public void SetFocus()
{
var textbox = this.editor.Template.FindName("Text", editor) as TextBox;
textbox.Focus();
}
You can Write extension and set custom property for textbox to make it focusable
For example you can write extension class as below
public static class FocusBehavior
{
#region Constants
public static readonly DependencyProperty IsFocusedProperty =
DependencyProperty.RegisterAttached("IsFocused", typeof (bool?),
typeof (FocusBehavior), new FrameworkPropertyMetadata(IsFocusedChanged));
#endregion
#region Public Methods
public static bool GetIsFocused(DependencyObject obj)
{
return (bool) obj.GetValue(IsFocusedProperty);
}
public static void SetIsFocused(DependencyObject obj, bool value)
{
obj.SetValue(IsFocusedProperty, value);
}
#endregion
#region Event Handlers
private static void IsFocusedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var uie = (UIElement) d;
if ((bool) e.NewValue)
uie.Dispatcher.BeginInvoke(DispatcherPriority.Input, new ThreadStart(() => Keyboard.Focus(uie)));
}
#endregion Event Handlers
}
Then in xaml as below:
<UserControl xmlns:behaviours="clr-namespace:Example.Views.Behaviours">
<TextBox TextWrapping="Wrap" Text="TextBox" behaviours:FocusBehavior.IsFocused={Binding IsFocused}/>
I hope that answeres your question

WPF Expander Validation

Does anyone know of a way to change the style of an expander if a IDataError validation occurs in a control held within the expander. E.g.
<Expander Header="Details">
<TextBox Text="{Binding Brand.DESCRIPTION,
UpdateSourceTrigger=LostFocus,
ValidatesOnDataErrors=True}"/>
</Expander>
So if the textbox has an error the style of my expander will change (go red maybe).
I'm looking to make this as generic as possible so without binding to each control within the expander manually if possible.
You could make use of the Attached Event Validation.Error (which is raised everytime a validation error is added or removed) through an Attached Behavior. To make this work you need to add NotifyOnValidationError=True to the bindings.
This Attached Behavior, ChildValidation, subscribes to the Validation.Error event for the Expander which is bubbled up if NotifyOnValidationError is set to True on the bindings. Since several Controls may be located within the Expander it also need to keep track of the count of Validation Errors that's currently active to determine if a Red Border should be displayed or not. It could look like this
Xaml
<Expander Header="Details"
behaviors:ChildValidationBehavior.ChildValidation="True">
<TextBox Text="{Binding Brand.DESCRIPTION,
UpdateSourceTrigger=LostFocus,
ValidatesOnDataErrors=True,
NotifyOnValidationError=True}"/>
</Expander>
ChildValidationBehavior
public static class ChildValidationBehavior
{
private static readonly DependencyProperty ErrorCountProperty =
DependencyProperty.RegisterAttached("ErrorCount",
typeof(int),
typeof(ChildValidationBehavior));
private static void SetErrorCount(DependencyObject element, int value)
{
element.SetValue(ErrorCountProperty, value);
}
private static int GetErrorCount(DependencyObject element)
{
return (int)element.GetValue(ErrorCountProperty);
}
public static readonly DependencyProperty ChildValidationProperty =
DependencyProperty.RegisterAttached("ChildValidation",
typeof(bool),
typeof(ChildValidationBehavior),
new UIPropertyMetadata(false, OnChildValidationPropertyChanged));
public static bool GetChildValidation(DependencyObject obj)
{
return (bool)obj.GetValue(ChildValidationProperty);
}
public static void SetChildValidation(DependencyObject obj, bool value)
{
obj.SetValue(ChildValidationProperty, value);
}
private static void OnChildValidationPropertyChanged(DependencyObject dpo,
DependencyPropertyChangedEventArgs e)
{
Control control = dpo as Control;
if (control != null)
{
if ((bool)e.NewValue == true)
{
SetErrorCount(control, 0);
Validation.AddErrorHandler(control, Validation_Error);
}
else
{
Validation.RemoveErrorHandler(control, Validation_Error);
}
}
}
private static void Validation_Error(object sender, ValidationErrorEventArgs e)
{
Control control = sender as Control;
if (e.Action == ValidationErrorEventAction.Added)
{
SetErrorCount(control, GetErrorCount(control)+1);
}
else
{
SetErrorCount(control, GetErrorCount(control)-1);
}
int errorCount = GetErrorCount(control);
if (errorCount > 0)
{
control.BorderBrush = Brushes.Red;
}
else
{
control.ClearValue(Control.BorderBrushProperty);
}
}
}

Categories