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)
Related
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>
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>
I am creating a custom blend behavior to act on a ListboxItem i.e.
public class DragtestBehaviour : Behavior<ListBoxItem>
{
public DragtestBehaviour()
{ // Insert code required on object creation below this point.
}
protected override void OnAttached()
{
base.OnAttached();
// Insert code that you would want run when the Behavior is attached to an object.
}
protected override void OnDetaching()
{
base.OnDetaching();
// Insert code that you would want run when the Behavior is removed from an object.
}
}
What I am not able to figure out is how do I attach it to the listboxitem?
Alternatively, do I have to style the Listbox item and attach it to Border or any other element in style? If this is true then do I have to also derive my behavior class from Border (i.e. Framework element)?
DragtestBehaviour : Behavior<Frameworkelement>
I have basically done what eran otzap suggested in his comment, and what is described in this article by Mark Smith. This allows to still use Blend Behaviors with its benefits (like AssociatedObject), but be more flexible about attaching it. It's a fusion of normal attached behavior with blend behavior.
For completeness sake, here's the relevant code as well.
Attach the behavior in ItemContainerStyle:
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}" BasedOn="{StaticResource {x:Type ListBoxItem}}">
<Setter Property="behaviors:DragDropBehavior.IsAttached" Value="True" />
</Style>
</ListBox.ItemContainerStyle>
Behavior
public class DragDropBehavior : Behavior<ListBoxItem>
{
// IsAttached
public static DependencyProperty IsAttachedProperty = DependencyProperty.RegisterAttached("IsAttached",typeof(bool), typeof(DragDropBehavior),new FrameworkPropertyMetadata(false, OnIsAttachedChanged));
public static bool GetIsAttached(DependencyObject o){return (bool)o.GetValue(IsAttachedProperty);}
public static void SetIsAttached(DependencyObject o, bool value){o.SetValue(IsAttachedProperty, value);}
// is called the same as when attached in Interaction.Behaviors tag
protected override void OnAttached()
{
base.OnAttached();
}
// manual attachement to listbox item
private static void OnIsAttachedChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
var el = o as UIElement;
if (el != null)
{
var behColl = Interaction.GetBehaviors(el);
var existingBehavior = behColl.FirstOrDefault(b => b.GetType() == typeof(DragDropBehavior)) as DragDropBehavior;
if ((bool)e.NewValue == false && existingBehavior != null)
{
behColl.Remove(existingBehavior);
}
else if ((bool)e.NewValue == true && existingBehavior == null)
{
behColl.Add(new DragDropBehavior());
}
}
}
}
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>
I have a usercontrol that expose a public property like this :
public Double ButtonImageHeight
{
get { return imgButtonImage.Height; }
set { imgButtonImage.Height = value; }
}
when I use that control, I want to be able to set that property throught a Style like that :
<Style x:Key="MyButtonStyle" TargetType="my:CustomButtonUserControl" >
<Setter Property="ButtonImageHeight" Value="100" />
</Style>
What am I doing wrong?
Thanks
thanks Matt, I just found it myself but you were absolutely right... here's the exact code I used in case it can help someone else (all the examples I found were on WPF, silverlight is just slightly different) :
public static readonly DependencyProperty ButtonImageHeightProperty = DependencyProperty.Register("ButtonImageHeight", typeof(Double), typeof(CustomButtonUserControl),new PropertyMetadata(ButtonImageHeight_PropertyChanged ));
public Double ButtonImageHeight
{
get { return (Double)GetValue(ButtonImageHeightProperty); }
set { SetValue(ButtonImageHeightProperty, value); }
}
private static void ButtonImageHeight_PropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
((CustomButtonUserControl)source).imgButtonImage.Height = (Double)e.NewValue;
}
The property needs to be a dependency property in order to support styles.
You can make it even more generic and nice by passing through a Style for your imgButtonImage, that way you can set multiple properties. So within your user control add the dependency property, but make it a Style:
public static readonly DependencyProperty UseStyleProperty =
DependencyProperty.Register("UseStyle", typeof(Style), typeof(CustomButtonUserControl), new PropertyMetadata(UseStyle_PropertyChanged));
public Style UseStyle
{
get { return (Style)GetValue(UseStyleProperty); }
set { SetValue(UseStyleProperty, value); }
}
private static void UseStyle_PropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
((CustomButtonUserControl)source).imgButtonImage.Style = (Style)e.NewValue;
}
Notice how within the PropertyChanged function I set the style of the control to the new style.
Then when I host the UserControl I can pass through the style:
<Style x:Name="MyFancyStyle" TargetType="Button" >
<Setter Property="FontSize" Value="24" />
</Style>
<controls:MyUserControl UseStyle="{StaticResource MyFancyStyle}" />
works in VS design mode too! (It's a miracle)