Binding style gives error - wpf

I have written code to attach a property Mask.But iam getting the error
Cannot resolve the Style Property 'Marsk'. Verify that the owning type is the Style's TargetType, or use Class.Property syntax to specify the Property.)
public static MaskType GetMask(DependencyObject obj)
{
return (MaskType)obj.GetValue(MaskProperty);
}
public static void SetMask(DependencyObject obj, MaskType value)
{
obj.SetValue(MaskProperty, value);
}
public static readonly DependencyProperty MaskProperty =
DependencyProperty.RegisterAttached(
"Mask",
typeof(MaskType),
typeof(pMaskableTextBox),
new FrameworkPropertyMetadata(MaskChangedCallback)
);
private static void MaskChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.OldValue is TextBox)
{
(e.OldValue as TextBox).PreviewTextInput -= TextBox_PreviewTextInput;
DataObject.RemovePastingHandler((e.OldValue as TextBox), (DataObjectPastingEventHandler)TextBoxPastingEventHandler);
}
TextBox _this = (d as TextBox);
if (_this == null)
return;
if ((MaskType)e.NewValue != MaskType.Any)
{
_this.PreviewTextInput += TextBox_PreviewTextInput;
DataObject.AddPastingHandler(_this, (DataObjectPastingEventHandler)TextBoxPastingEventHandler);
}
ValidateTextBox(_this);
}
i have called this property in styles rStyles.XAML
xmlns:pBasePage="clr-namespace:Parts.Pages.Page;assembly=Parts.Pages.Page"
<Style x:Key="styDecimalTextBox" TargetType="{x:Type TextBox}">
<Setter Property="pBasePage:Parts.Pages.Page.pMaskableTextBox.Mask" Value ="Decimal" />
</Style>

The error says it all really:
Cannot resolve the Style Property 'Mask'. Verify that the owning type
is the Style's TargetType, or use Class.Property syntax to specify the
Property.)
Check the TargetType:
<Style x:Key="styDecimalTextBox" TargetType="{x:Type YourTextBoxWithTheMaskProperty}">
<Setter Property="pBasePage:Parts.Pages.Page.pMaskableTextBox.Mask" Value ="Decimal" />
</Style>

Related

WPF ToolBar - Detect when item is set into ToolBarOverflowPanel

I am aware of the IsOverflowOpen and HasOverflowItems properties but I am looking for a way to tell whether the item (button, radiobutton...) has moved into the ToolBarOverflowPanel so I can use a trigger to change its style.
I need this to be able to reproduce some UWP ToolBar styles (Windows 10 Mail, Word, Excel...). I have successfully reproduced most of the style and the only missing bit is to be able to changed the style of my item when it is in the overflow panel.
On the screenshot of what I am trying to reproduce, you can clearly see that the Special Ident and Line Spacing buttons have changed style based on whether they are displayed or overflowed.
You can't do it with xaml only. You have to either use the code behind or create some attached properties.
Here is the solution with the AttachedProperty:
First you need to create an helper class exposing 2 properties:
The IsInOverflowPanel read-only property that you will use to trigger the style change.
The TrackParentPanel property, which is the enable/disable mechanism.
Here is the implementation:
public static class ToolBarHelper
{
public static readonly DependencyPropertyKey IsInOverflowPanelKey =
DependencyProperty.RegisterAttachedReadOnly("IsInOverflowPanel", typeof(bool), typeof(ToolBarHelper), new PropertyMetadata(false));
public static readonly DependencyProperty IsInOverflowPanelProperty = IsInOverflowPanelKey.DependencyProperty;
[AttachedPropertyBrowsableForType(typeof(UIElement))]
public static bool GetIsInOverflowPanel(UIElement target)
{
return (bool)target.GetValue(IsInOverflowPanelProperty);
}
public static readonly DependencyProperty TrackParentPanelProperty =
DependencyProperty.RegisterAttached("TrackParentPanel", typeof(bool), typeof(ToolBarHelper),
new PropertyMetadata(false, OnTrackParentPanelPropertyChanged));
public static void SetTrackParentPanel(DependencyObject d, bool value)
{
d.SetValue(TrackParentPanelProperty, value);
}
public static bool GetTrackParentPanel(DependencyObject d)
{
return (bool)d.GetValue(TrackParentPanelProperty);
}
private static void OnTrackParentPanelPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var element = d as UIElement;
if (element != null)
{
bool newValue = (bool)e.NewValue;
if (newValue)
{
element.LayoutUpdated += (s, arg) => OnControlLayoutUpdated(element);
}
}
}
private static void OnControlLayoutUpdated(UIElement element)
{
var isInOverflow = TreeHelper.FindParent<ToolBarOverflowPanel>(element) != null;
element.SetValue(IsInOverflowPanelKey, isInOverflow);
}
}
public static class TreeHelper
{
public static T FindParent<T>(this DependencyObject obj) where T : DependencyObject
{
return obj.GetAncestors().OfType<T>().FirstOrDefault();
}
public static IEnumerable<DependencyObject> GetAncestors(this DependencyObject element)
{
do
{
yield return element;
element = VisualTreeHelper.GetParent(element);
} while (element != null);
}
}
Then, for every items which need to change style do the following:
<Button x:Name="DeleteButton" Content="Delete" helpers:ToolBarHelper.TrackParentPanel="True">
<Button.Style>
<Style BasedOn="{StaticResource {x:Static ToolBar.ButtonStyleKey}}" TargetType="{x:Type Button}">
<Style.Triggers>
<Trigger Property="helpers:ToolBarHelper.IsInOverflowPanel" Value="True">
<!-- The Overflow style setters -->
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>

How to style subclassed components in WPF

I have a subclass of TextBox in WPF/XAML and I wish to apply the same styles to it as I do with all my other TextBox instances. I have the following styles defined
<Style TargetType="TextBox" x:Key="basicTextBox" >
<Setter Property="Controls:TextBoxBehaviours.UpdateWhenEnterPressed" Value="True"/>
<Setter Property="Controls:TextBoxBehaviours.SelectAllWhenEnterPressed" Value="True"/>
<Setter Property="Controls:TextBoxBehaviours.SelectAllOnFocus" Value="True"/>
</Style>
and a class TextBoxBehaviours to implement these
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Reactive.Linq;
namespace Weingartner.Controls
{
public static class TextBoxBehaviours
{
static TextBoxBehaviours()
{}
#region Binding Support
private static Dictionary<Tuple<TextBox,string>, IDisposable> Bindings
= new Dictionary<Tuple<TextBox,string>, IDisposable>();
private static void Bind(TextBox tb, string key, IDisposable d)
{
Bindings[Tuple.Create(tb, key)] = d;
}
private static void UnBind(TextBox tb, string key)
{
var t = Tuple.Create(tb, key);
if (Bindings.ContainsKey(t))
{
var d = Bindings[t];
Bindings.Remove(t);
d.Dispose();
}
}
#endregion
static UIPropertyMetadata CreateMeta<T>(bool defaultValue, Action<T,DependencyPropertyChangedEventArgs> fn)
where T : DependencyObject
{
return new UIPropertyMetadata(defaultValue, (o, e) =>{
var t = o as T;
if (t == null)
{
return;
}
fn(t, e);
});
}
#region Update When Enter Pressed
public static readonly DependencyProperty
UpdateWhenEnterPressedProperty =
DependencyProperty.RegisterAttached
( "UpdateWhenEnterPressed"
, typeof(bool)
, typeof(TextBoxBehaviours)
, CreateMeta<TextBox>(false, SetupUpdateOnEnterPressed));
public static void
SetUpdateWhenEnterPressed
( TextBox dp
, bool value)
{
dp.SetValue(UpdateWhenEnterPressedProperty, value);
}
public static bool
GetUpdateWhenEnterPressed
( TextBox dp)
{
return (bool)dp.GetValue(UpdateWhenEnterPressedProperty);
}
private static void
SetupUpdateOnEnterPressed
( TextBox element
, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue)
{
var sub = element
.PreviewKeyDownObserver()
.Where(x=>x.EventArgs.Key==Key.Enter)
.Subscribe(x=>DoUpdateSource(element));
Bind(element, "KeyEnter", sub);
}else{
UnBind(element, "KeyEnter");
}
}
static void DoUpdateSource(TextBox source)
{
BindingExpression binding = BindingOperations.GetBindingExpression(source, TextBox.TextProperty);
if (binding != null)
{
binding.UpdateSource();
}
}
#endregion
#region SelectAll When Enter Pressed
public static readonly DependencyProperty
SelectAllWhenEnterPressedProperty =
DependencyProperty.RegisterAttached
( "SelectAllWhenEnterPressed"
, typeof(bool)
, typeof(TextBoxBehaviours)
, CreateMeta<TextBox>(false, SetupSelectAllOnEnterPressed));
public static void
SetSelectAllWhenEnterPressed
( TextBox dp
, bool value)
{
dp.SetValue(SelectAllWhenEnterPressedProperty, value);
}
public static bool
GetSelectAllWhenEnterPressed
( TextBox dp)
{
return (bool)dp.GetValue(SelectAllWhenEnterPressedProperty);
}
private static void
SetupSelectAllOnEnterPressed
( TextBox element
, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue)
{
var sub = element
.PreviewKeyDownObserver()
.Where(x=>x.EventArgs.Key==Key.Enter)
.Subscribe(x=>element.SelectAll());
Bind(element, "KeyEnterSelectAll", sub);
}else{
UnBind(element, "KeyEnterSelectAll");
}
}
#endregion
#region Select All On Focus
public static readonly DependencyProperty
SelectAllOnFocusProperty =
DependencyProperty.RegisterAttached
( "SelectAllOnFocus"
, typeof(bool)
, typeof(TextBoxBehaviours)
, CreateMeta<TextBox>(false, SetupSelectAllOnFocus));
public static void
SetSelectAllOnFocus
( TextBox dp
, bool value)
{
dp.SetValue(SelectAllOnFocusProperty, value);
}
public static bool
GetSelectAllOnFocus
( TextBox dp)
{
return (bool)dp.GetValue(SelectAllOnFocusProperty);
}
private static void
SetupSelectAllOnFocus
( TextBox element
, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue)
{
Bind(element, "Focus", element.GotFocusObserver().Subscribe(x => element.SelectAll()));
}else{
UnBind(element, "Focus");
}
}
#endregion
}
}
in my XAML files I have been doing
<Style TargetType="TextBox" BasedOn="{StaticResource basicTextBox}"/>
and all following TextBoxes get the behviour I wish. Given my subclass is also a TextBox class I assumed that these would also get the behaviour but they don't. I tried being explicit by extending the styles so
<Style TargetType="TextBox" BasedOn="{StaticResource basicTextBox}"/>
<Style TargetType="Controls:EditForLength">
<Setter Property="Controls:TextBoxBehaviours.UpdateWhenEnterPressed" Value="True"/>
<Setter Property="Controls:TextBoxBehaviours.SelectAllWhenEnterPressed" Value="True"/>
<Setter Property="Controls:TextBoxBehaviours.SelectAllOnFocus" Value="True"/>
</Style>
but I get an error that tells me the key for EditForLength
System.ArgumentException occurred
HResult=-2147024809
Message=Item has already been added. Key in dictionary: 'Weingartner.Controls.EditForLength' Key being added: 'Weingartner.Controls.EditForLength'
Source=mscorlib
StackTrace:
at System.Collections.Hashtable.Insert(Object key, Object nvalue, Boolean add)
InnerException:
So in summary behaviours installed via styles on a parent class do not propogate to the child class instances but when it is attempted to add another style targeting the child class there is some dictionary error.
Note I have tried the behaviours explicity on the instances of the EditForLength control they work fine. I just can't install the behaviours via styling.
UPDATE
Have discovered that I get the error even when all important bits
except the style declaration are commented out
<!--<Style TargetType="TextBox" BasedOn="{StaticResource basicTextBox}"/>-->
<Style TargetType="Controls:EditForLength">
<!--
<Setter Property="Controls:TextBoxBehaviours.UpdateWhenEnterPressed" Value="True"/>
<Setter Property="Controls:TextBoxBehaviours.SelectAllWhenEnterPressed" Value="True"/>
<Setter Property="Controls:TextBoxBehaviours.SelectAllOnFocus" Value="True"/>
-->
</Style>
About exception: it seems that you already added this style in dictionary (or its merged dictionaries) - try to search for it, I'm sure you will find a style with the same key.
About implicit styles: they are not applied to subclasses of target type (try to imagine if they are applied - if you have created implicit style for FrameworkElement - layout of a huge part of your controls will be broken)

How to know when textbox is being modified?

I'm using the Delay binding tag of .Net 4.5 but I want to change the textbox's background color while the changes are not "committed". How can I set an IsDirty property to true while the delay is happening?
I tried using the TextChanged event to set an IsDirty flag and then remove the flag when the bound property got set. The problem is that the TextChanged fires whenever the bound property changes and not just when the user modifies the text.
I got it "working" in a very clunky and fragile way by monitoring the TextChanged event and the bound property. Needless to say this is very prone to bugs so I would like a cleaner solution. Is there any way to know that the textbox has been changed but not committed yet (by the Delay)?
I had a look through the source code and the BindingExpressionBase itself is aware of this through a property called NeedsUpdate. But this property is internal so you would have to use reflection to get it.
However, you won't be able to monitor this property in any easy way. So the way I see it, you would need to use both of the events TextChanged and SourceUpdated to know when NeedsUpdate might have changed.
Update
I created an attached behavior that does this, it can be used to monitor pending updates on any DependencyProperty. Note that NotifyOnSourceUpdated must be set to true.
Uploaded a small sample project here: PendingUpdateExample.zip
Example
<TextBox Text="{Binding ElementName=textBoxSource,
Path=Text,
NotifyOnSourceUpdated=True,
UpdateSourceTrigger=PropertyChanged,
Delay=1000}"
ab:UpdatePendingBehavior.MonitorPendingUpdates="{x:Static TextBox.TextProperty}">
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<Trigger Property="ab:UpdatePendingBehavior.HasPendingUpdates"
Value="True">
<Setter Property="Background" Value="Green"/>
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
UpdatePendingBehavior
public class UpdatePendingBehavior
{
#region MonitorPendingUpdates
public static DependencyProperty MonitorPendingUpdatesProperty =
DependencyProperty.RegisterAttached("MonitorPendingUpdates",
typeof(DependencyProperty),
typeof(UpdatePendingBehavior),
new UIPropertyMetadata(null, MonitorPendingUpdatesChanged));
public static DependencyProperty GetMonitorPendingUpdates(FrameworkElement obj)
{
return (DependencyProperty)obj.GetValue(MonitorPendingUpdatesProperty);
}
public static void SetMonitorPendingUpdates(FrameworkElement obj, DependencyProperty value)
{
obj.SetValue(MonitorPendingUpdatesProperty, value);
}
public static void MonitorPendingUpdatesChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
DependencyProperty property = e.NewValue as DependencyProperty;
if (property != null)
{
FrameworkElement element = target as FrameworkElement;
element.SourceUpdated += elementProperty_SourceUpdated;
if (element.IsLoaded == true)
{
SubscribeToChanges(element, property);
}
element.Loaded += delegate { SubscribeToChanges(element, property); };
element.Unloaded += delegate { UnsubscribeToChanges(element, property); };
}
}
private static void SubscribeToChanges(FrameworkElement element, DependencyProperty property)
{
DependencyPropertyDescriptor propertyDescriptor =
DependencyPropertyDescriptor.FromProperty(property, element.GetType());
propertyDescriptor.AddValueChanged(element, elementProperty_TargetUpdated);
}
private static void UnsubscribeToChanges(FrameworkElement element, DependencyProperty property)
{
DependencyPropertyDescriptor propertyDescriptor =
DependencyPropertyDescriptor.FromProperty(property, element.GetType());
propertyDescriptor.RemoveValueChanged(element, elementProperty_TargetUpdated);
}
private static void elementProperty_TargetUpdated(object sender, EventArgs e)
{
FrameworkElement element = sender as FrameworkElement;
UpdatePendingChanges(element);
}
private static void elementProperty_SourceUpdated(object sender, DataTransferEventArgs e)
{
FrameworkElement element = sender as FrameworkElement;
if (e.Property == GetMonitorPendingUpdates(element))
{
UpdatePendingChanges(element);
}
}
private static void UpdatePendingChanges(FrameworkElement element)
{
BindingExpressionBase beb = BindingOperations.GetBindingExpressionBase(element, GetMonitorPendingUpdates(element));
BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic;
PropertyInfo needsUpdateProperty = beb.GetType().GetProperty("NeedsUpdate", bindingFlags);
SetHasPendingUpdates(element, (bool)needsUpdateProperty.GetValue(beb));
}
#endregion // MonitorPendingUpdates
#region HasPendingUpdates
public static DependencyProperty HasPendingUpdatesProperty =
DependencyProperty.RegisterAttached("HasPendingUpdates",
typeof(bool),
typeof(UpdatePendingBehavior),
new UIPropertyMetadata(false));
public static bool GetHasPendingUpdates(FrameworkElement obj)
{
return (bool)obj.GetValue(HasPendingUpdatesProperty);
}
public static void SetHasPendingUpdates(FrameworkElement obj, bool value)
{
obj.SetValue(HasPendingUpdatesProperty, value);
}
#endregion // HasPendingUpdates
}
Another way could be to use a MultiBinding that binds both to the source and the target and compares their values in a converter. Then you could change the Background in the Style. This assumes that you don't convert the value. Example with two TextBoxes
<TextBox Text="{Binding ElementName=textBoxSource,
Path=Text,
UpdateSourceTrigger=PropertyChanged,
Delay=2000}">
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Value="False">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource IsTextEqualConverter}">
<Binding RelativeSource="{RelativeSource Self}"
Path="Text"/>
<Binding ElementName="textBoxSource" Path="Text"/>
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Background" Value="Green"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<TextBox Name="textBoxSource"/>
IsTextEqualConverter
public class IsTextEqualConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values[0].ToString() == values[1].ToString();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
You could try some other event like PreviewTextInput or one of the key related ones. (You probably need the tunneling versions as bubbling events are probably handled internally)
I'm not sure about the Delay binding. However, in .Net 4.0 I'd use a BindingGroup. BindingGroup has a CanRestoreValues property which will report exactly what you want: If the UpdateSourceTrigger is Explicit (which is default if there is a BindingGroup), CanRestoreValues will be true from the time one bound control value has been changed until BindingGroup.CommitEdit is called and the values are forwarded to the bound object. However, CanRestoreValues will be true as soon as any control that shares the BindingGroup has a pending value, so if you want feedback for each separate property you will have to use one BindingGroup per control, which makes calling CommitEdit a little bit less convenient.

refactoring to an attached property

I am trying to change the code below from being an event setter to an attached property (just so I can clean up the code behind). I get an error in the setter saying the value cannot be null, but I don't see why yet.
Forgetting for a second whether this is a good idea or not, can someone help me get the attached property right?
Cheers,
Berryl
EventSetter (works but with code behind as shown)
<!-- SINGLE CLICK EDITING -->
<Style TargetType="{x:Type DataGridCell}">
<EventSetter Event="PreviewMouseLeftButtonDown" Handler="OnPreviewMouseLeftButtonDown"/>
</Style>
private void OnPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var cell = sender as DataGridCell;
cell.Activate();
}
Property setter (error)
<!-- SINGLE CLICK EDITING -->
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="begavior:DataGridCellProperties.SingleClickToEdit" Value="True"/>
</Style>
public class DataGridCellProperties
{
public static readonly DependencyProperty SingleClickToEditProperty =
DependencyProperty.RegisterAttached("SingleClickToEditProperty",
typeof(bool), typeof(DataGridCellProperties),
new PropertyMetadata(false, OnSingleClickToEditPropertyChanged));
[AttachedPropertyBrowsableForChildren(IncludeDescendants = false)]
[AttachedPropertyBrowsableForType(typeof(DataGridCell))]
public static bool GetSingleClickToEdit(DataGridCell obj) { return (bool)obj.GetValue(SingleClickToEditProperty); }
public static void SetSingleClickToEdit(DataGridCell obj, bool value) { obj.SetValue(SingleClickToEditProperty, value); }
private static void OnSingleClickToEditPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var sender = obj as UIElement;
if (sender == null) return;
if ((bool)e.NewValue)
{
sender.PreviewMouseLeftButtonDown += OnPreviewMouseLeftButtonDown_EditCell;
}
else
{
sender.PreviewMouseLeftButtonDown -= OnPreviewMouseLeftButtonDown_EditCell;
}
}
private static void OnPreviewMouseLeftButtonDown_EditCell(object sender, MouseButtonEventArgs e)
{
var cell = sender as DataGridCell;
cell.Activate();
}
}
"SingleClickToEditProperty" in your d-prop registration should be "SingleClickToEdit".

Merge control style with global style set by another project dynamically

I currently am facing a problem. I am using WPF.Themes which i found on codeplex, it allows me to change my application's theme.
So I imported the project and got it all working fine, but for some control, say my treeViewItem, I had style already set to it which it overrides the global styles.
I have the following code after research but still won't work.
<TreeView Name="_tvTreeView" Grid.Row="1" >
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}" BasedOn="{StaticResource {x:Type TreeViewItem}}">
<EventSetter Event="MouseDoubleClick" Handler="tvTreeView_PreviewMouseDoubleClick"/>
<EventSetter Event="MouseDown" Handler="tvTreeView_MouseDown"/>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
The based on works if I manully add the resrouce file in the merge dictionary of app.xaml of my Main Project.
But WPF.Themes project allow me to change theme dynamically by doing this.
public static void ApplyTheme(this ContentControl control, string theme)
{
ResourceDictionary dictionary = ThemeManager.GetThemeResourceDictionary(theme);
control.Resources.MergedDictionaries.Clear();
if (dictionary != null)
control.Resources.MergedDictionaries.Add(dictionary);
}
Having the above code, does not merge the global styles and my event setters.
If I manually reference the theme in app.xaml then "BasedOn" would kick in and work, but "BasedOn" don't seem to work if I set the mergedDictionaries dynamically.
Is there a way I can get this to work without adding the theme to app.xaml.
Thanks and Regards,
The BaseOn property of style cannot be set with DynamicResource, with StaticResource, it will be sealed when applied to control.
You should merge the style when global style changed, try these codes:
public class Behavior
{
#region AutoMergeStyle
public static readonly DependencyProperty AutoMergeStyleProperty =
DependencyProperty.RegisterAttached("AutoMergeStyle", typeof(bool), typeof(Behavior),
new FrameworkPropertyMetadata((bool)false,
new PropertyChangedCallback(OnAutoMergeStyleChanged)));
public static bool GetAutoMergeStyle(DependencyObject d)
{
return (bool)d.GetValue(AutoMergeStyleProperty);
}
public static void SetAutoMergeStyle(DependencyObject d, bool value)
{
d.SetValue(AutoMergeStyleProperty, value);
}
private static void OnAutoMergeStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.OldValue == e.NewValue)
{
return;
}
Control control = d as Control;
if (control == null)
{
throw new NotSupportedException("AutoMergeStyle can only used in Control");
}
if ((bool)e.NewValue)
{
Type type = d.GetType();
control.SetResourceReference(Behavior.BaseOnStyleProperty, type);
}
else
{
control.ClearValue(Behavior.BaseOnStyleProperty);
}
}
#endregion
#region BaseOnStyle
public static readonly DependencyProperty BaseOnStyleProperty =
DependencyProperty.RegisterAttached("BaseOnStyle", typeof(Style), typeof(Behavior),
new FrameworkPropertyMetadata((Style)null,
new PropertyChangedCallback(OnBaseOnStyleChanged)));
public static Style GetBaseOnStyle(DependencyObject d)
{
return (Style)d.GetValue(BaseOnStyleProperty);
}
public static void SetBaseOnStyle(DependencyObject d, Style value)
{
d.SetValue(BaseOnStyleProperty, value);
}
private static void OnBaseOnStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.OldValue == e.NewValue)
{
return;
}
Control control = d as Control;
if (control == null)
{
throw new NotSupportedException("BaseOnStyle can only used in Control");
}
Style baseOnStyle = e.NewValue as Style;
Style originalStyle = GetOriginalStyle(control);
if (originalStyle == null)
{
originalStyle = control.Style;
SetOriginalStyle(control, originalStyle);
}
Style newStyle = originalStyle;
if (originalStyle.IsSealed)
{
newStyle = new Style();
newStyle.TargetType = originalStyle.TargetType;
//1. Copy resources, setters, triggers
newStyle.Resources = originalStyle.Resources;
foreach (var st in originalStyle.Setters)
{
newStyle.Setters.Add(st);
}
foreach (var tg in originalStyle.Triggers)
{
newStyle.Triggers.Add(tg);
}
//2. Set BaseOn Style
newStyle.BasedOn = baseOnStyle;
}
else
{
originalStyle.BasedOn = baseOnStyle;
}
control.Style = newStyle;
}
#endregion
#region OriginalStyle
public static readonly DependencyProperty OriginalStyleProperty =
DependencyProperty.RegisterAttached("OriginalStyle", typeof(Style), typeof(Behavior),
new FrameworkPropertyMetadata((Style)null));
public static Style GetOriginalStyle(DependencyObject d)
{
return (Style)d.GetValue(OriginalStyleProperty);
}
public static void SetOriginalStyle(DependencyObject d, Style value)
{
d.SetValue(OriginalStyleProperty, value);
}
#endregion
}
Add attached property AutoMergeStyle to xaml:
<Style TargetType="{x:Type TreeViewItem}" BasedOn="{StaticResource {x:Type TreeViewItem}}">
<EventSetter Event="MouseDoubleClick" Handler="tvTreeView_PreviewMouseDoubleClick"/>
<EventSetter Event="MouseDown" Handler="tvTreeView_MouseDown"/>
<Setter Property="Behavior.AutoMergeStyle" Property="True"/>
</Style>

Resources