How to set a DepedencyProperty by a style setter? - wpf

My user control have the following DP:
public static readonly DependencyProperty ButtonAnimationColorProperty =
DependencyProperty.Register("ButtonAnimationColor", typeof(Color), typeof(MyControl),
new FrameworkPropertyMetadata(Colors.RoyalBlue, FrameworkPropertyMetadataOptions.AffectsRender, ThemeUpdate));
public Color ButtonAnimationColor
{
get { return (Color)GetValue(ButtonAnimationColorProperty ); }
set { SetValue(ButtonAnimationColorProperty , value); }
}
This control is compiled into a dll, that I use in others solutions. It works perfect well when I set directly:
<ns:MyControl ButtonAnimationColor="Green" />
The problem occurs when I try to set this DP by using a Style Setter, like that:
<ns:MyControl>
<ns:MyControl.Style>
<Style>
<Setter Property="ButtonAnimationColor" Value="Green" />
</Style>
</ns:MyControl.Style>
</ns:MyControl>
It give me the following error:
The member "ButtoAnimationColor" is not recognized or is not acessible.
What changes I need to make in my code to be able to set the property like that?

Try setting the target type for the style:
<ns:MyControl.Style>
<Style TargetType="{x:Type ns:MyControl}">
<Setter Property="ButtonAnimationColor" Value="Green" />
</Style>
</ns:MyControl.Style>

Related

WPF: designer won't set custom property

In my WPF appp, I created a custom control by subclassing TextBox. I then added a DependencyProperty called BorderWhenRequired (the border to use when the TextBox represents a required field), like so:
public class TextBoxEx : TextBox
{
public static readonly DependencyProperty BorderWhenRequiredProperty = DependencyProperty.Register(
"BorderWhenRequired", typeof(Brush), typeof(TextBoxEx),
new FrameworkPropertyMetadata(default(Brush), FrameworkPropertyMetadataOptions.AffectsRender)
);
public Brush BorderWhenRequired
{
get { return (Brush)GetValue(BorderWhenRequiredProperty); }
set { SetValue(BorderWhenRequiredProperty, value); }
}
}
I then created a resource that holds the brush value for the border, and a style for the control:
<SolidColorBrush x:Key="RequiredControlBorderBrush">Purple</SolidColorBrush>
<Style TargetType="{x:Type implementations:TextBoxEx}">
<Setter Property="BorderWhenRequired" Value="{StaticResource RequiredControlBorderBrush}" />
</Style>
but the designer is showing me:
ArgumentException: '#FF800080' is not a valid value for the 'MyNs.Common.Controls.Implementations.TextBoxEx.BorderWhenRequired' property on a Setter.
I tried defining the resource RequiredControlBorderBrush as a Brush, Color and SolidColorBrush, but I keep getting the same error.
Solved my own problem. In case anyone comes across this: I made a careless mistake. All Color properties in my custom class where defined as System.Drawing.Brush, when they should have been System.Window.Media.Brush.

ControlTemplate Trigger don't work in designer (runtime ok)

I have a custom Control derived from Window:
class LVSDialog : Window
with DependencyProperty ShowCloseButton
and a Style with ControlTemplate and Trigger:
<Style TargetType="{x:Type loc:LVSDialog}" x:Key="LVSDialogStyle">
...
<Setter Property="Template">
...
<Button x:Name="closeButton" />
...
<ControlTemplate.Triggers>
<Trigger Property="loc:LVSDialog.ShowCloseButton" Value="False">
<Setter TargetName="closeButton" Property="Visibility" Value="Collapsed" />
</Trigger>
</ControlTemplate.Triggers>
</Setter>
Everything works fine in the runtime, but in Designer it doesn't take sence if I change this Property - Button is visible all the time:
<loc:LVSDialog ...
ShowCloseButton="False" Style="{StaticResource LVSDialogStyle}">
I have searched for a solution in google and here - all questions are about runtime functionality, designer problems are either unanswered or not working suggestions.
Is it possible at all use full functions in design time?
P.S. My VisualStudio is 2012. Framework 4.0
If you change the base class to Control instead of Window it will work:
public class LVSDialog : Control
{
public bool ShowCloseButton
{
get { return (bool)GetValue(ShowCloseButtonProperty); }
set { SetValue(ShowCloseButtonProperty, value); }
}
// Using a DependencyProperty as the backing store for ShowCloseButton. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ShowCloseButtonProperty =
DependencyProperty.Register("ShowCloseButton", typeof(bool), typeof(LVSDialog), new PropertyMetadata(true));
}
From Topicstarter:
I have changed to Control, added internal window, set it content to my control and added Show() and ShowDialog() methods:
private Window parentWindow;
...
public void Show()
{
if (parentWindow == null)
{
parentWindow = new Window {Content = this, WindowStyle = ...};
}
parentWindow.Show();
}
Everything works fine, designer shows all properties "live".

Reusable style for DataGridTextColumn in WPF [duplicate]

I tried to create a Style for DataGridTextColumn with the following code
<Style TargetType="{x:Type DataGridTextColumn}">
...
</Style>
However, Visual Studio 2010 highlights {x:Type DataGridTextColumn} with a blue line and elaborates: Exception has been thrown by the target of an invocation.
Why does this happen and how do I fix it?
You can't style the DataGridTextColumn because DataGridTextColumn does not derive from FrameworkElement (or FrameworkContentElement). Only FrameworkElement, etc supports styling.
When you attempt to create a style in XAML for any type that is not a FrameworkElement or FrameworkContentElement you get that error message.
How do you solve this? As with any problem, where there is a will there is a way. In this case I think the easiest solution is to create an attached property for DataGrid to assign a DataGridColumn style:
<DataGrid ...>
<local:MyDataGridHelper.TextColumnStyle>
<Style TargetType="FrameworkElement">
... setters here ...
</Style>
</local:MyDataGridHelper.TextColumnStyle>
...
The implementation would be something along these lines:
public class MyDataGridHelper : DependencyObject
{
// Use propa snipped to create attached TextColumnStyle with metadata:
... RegisterAttached("TextColumnStyle", typeof(Style), typeof(MyDataGridHelper), new PropertyMetadata
{
PropertyChangedCallback = (obj, e) =>
{
var grid = (DataGrid)obj;
if(e.OldValue==null && e.NewValue!=null)
grid.Columns.CollectionChanged += (obj2, e2) =>
{
UpdateColumnStyles(grid);
}
}
}
private void UpdateStyles(DataGrid grid)
{
var style = GetTextColumnStyle(grid);
foreach(var column in grid.Columns.OfType<DataGridTextColumn>())
foreach(var setter in style.Setters.OfType<Setter>())
if(setter.Value is BindingBase)
BindingOperations.SetBinding(column, setter.Property, setter.Value);
else
column.SetValue(setter.Property, setter.Value);
}
}
The way this works is, any time the attached property is changed, a handler is added for the Columns.CollectionChanged event on the grid. When the CollectionChanged event fires, all columns are updated with the style that was set.
Note that the above code does not handle the situation where a style is removed and re-added gracefully: Two event handlers are registered. For a really robust solution you would want to fix this by adding another attached property containing the event handler so the event handler could be unregistered, but for your purpose I think this is unimportant.
Another caveat here is that the direct use of SetBinding and SetValue will cause the DependencyProperty to have a BaseValueSource of Local instead of DefaultStyle. This will probably make no difference in your case but I thought I should mention it.
The style tag has to go in the right place. Your datagrid may look something like this right now:
<DataGrid>
<DataGrid.Columns>
<DataGridTextColumn />
</DataGrid.Columns>
</DataGrid>
You might initially try to add the style tag directly within the DataGridTextColumn element which will not work. You can however create elements for "DataGridTextColumn.ElementStyle" and or "DataGridTextColumn.EditingElementStyle" just within the "DataGridTextColumn" element. Each of those element tags can then have style tags within them:
<DataGrid>
<DataGrid.Columns>
<DataGridTextColumn>
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="Background" Value="Green"></Setter>
</Style>
</DataGridTextColumn.ElementStyle>
<DataGridTextColumn.EditingElementStyle>
<Style TargetType="TextBox">
<Setter Property="Background" Value="Orange"></Setter>
</Style>
</DataGridTextColumn.EditingElementStyle>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
One style will be applied to viewing and the other will be applied when the cell is in edit mode. Note that it changes from a TextBlock when viewing to a TextBox when editing (This got me at first!).
This is more an addition to Ray Burns answer. I first wasn't able to implement it on my own, but with help of mm8 (https://stackoverflow.com/a/46690951/5381620) I got it running. Works really fine. For other people who have problems following this attached property approach maybe a full code snippet is helpful.
public class MyDataGridHelper : DependencyObject
{
private static readonly DependencyProperty TextColumnStyleProperty = DependencyProperty.RegisterAttached("TextColumnStyle", typeof(Style), typeof(MyDataGridHelper), new PropertyMetadata
{
PropertyChangedCallback = (obj, e) =>
{
var grid = (DataGrid)obj;
if (e.OldValue == null && e.NewValue != null)
grid.Columns.CollectionChanged += (obj2, e2) =>
{
UpdateColumnStyles(grid);
};
}
});
public static void SetTextColumnStyle(DependencyObject element, Style value)
{
element.SetValue(TextColumnStyleProperty, value);
}
public static Style GetTextColumnStyle(DependencyObject element)
{
return (Style)element.GetValue(TextColumnStyleProperty);
}
private static void UpdateColumnStyles(DataGrid grid)
{
var origStyle = GetTextColumnStyle(grid);
foreach (var column in grid.Columns.OfType<DataGridTextColumn>())
{
//may not add setters to a style which is already in use
//therefore we need to create a new style merging
//original style with setters from attached property
var newStyle = new Style();
newStyle.BasedOn = column.ElementStyle;
newStyle.TargetType = origStyle.TargetType;
foreach (var setter in origStyle.Setters.OfType<Setter>())
{
newStyle.Setters.Add(setter);
}
column.ElementStyle = newStyle;
}
}
}
xaml
<Grid>
<DataGrid Name="MyDataGrid" ItemsSource="{Binding Lines}" AutoGenerateColumns="False" >
<local:MyDataGridHelper.TextColumnStyle>
<Style TargetType="TextBlock">
<Setter Property="TextWrapping" Value="Wrap"/>
</Style>
</local:MyDataGridHelper.TextColumnStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="ProductId1" Binding="{Binding Path=Result1}" />
<DataGridTextColumn Header="ProductId2" Binding="{Binding Path=Result2}" />
</DataGrid.Columns>
</DataGrid>
</Grid>
Edit: In first approach I did overwrite the whole style. In new version it is still possible to maintain other styles modifications like this one
<DataGridTextColumn.ElementStyle>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="Red"/>
</Style>
</DataGridTextColumn.ElementStyle>
A small addition to pedrito answer. Everything works fine, but if origStyle has base style, base style's setters get discarded.
To fix this, we need get all setters:
private static void UpdateColumnStyles(DataGrid grid)
{
var origStyle = GetTextColumnStyle(grid);
foreach (var column in grid.Columns.OfType<DataGridTextColumn>())
{
//may not add setters to a style which is already in use
//therefore we need to create a new style merging
//original style with setters from attached property
var newStyle = new Style();
newStyle.BasedOn = column.ElementStyle;
newStyle.TargetType = origStyle.TargetType;
var baseSetters = GetBaseSetters(origStyle);
var allSetters = baseSetters.Concat(origStyle.Setters.OfType<Setter>());
foreach (var setter in allSetters)
{
newStyle.Setters.Add(setter);
}
column.ElementStyle = newStyle;
}
}
private static IEnumerable<Setter> GetBaseSetters(Style style)
{
return style.BasedOn?.Setters.OfType<Setter>().Concat(GetBaseSetters(style.BasedOn)??new Setter[0]);
}
More simple:
<FontFamily x:Key="DefaultFont">Snap ITC</FontFamily>
<Style x:Key="ControlStyle" TargetType="Control">
<Setter Property="FontFamily" Value="{StaticResource DefaultFont}"/>
</Style>
<Style TargetType="{x:Type DataGridCellsPresenter}" BasedOn="{StaticResource ControlStyle}">
</Style>
A DataGridTextColumn is nothing but a column with a TextBlock in it. Write a style with the TargetType as TextBlock and bind the ElementStyle property of the DataGridTextColumn to it. Hope that helps!

Binding the Key in <object property="{StaticResource key}" .../> to a value

In WPF, is it possible to bind the key in "{StaticResource key}"to a variable.
For example. I have a variable ExecutionState with the states Active and Completed.
In my ResourceDictionary I have
<Style TargetType="{x:Type TextBlock}" x:Key="Active">
<Setter Property="Foreground" Value="Yellow"/>
</Style>
<Style TargetType="{x:Type TextBlock}" x:Key="Completed">
<Setter Property="Foreground" Value="Green"/>
</Style>
Instead of having
<TextBlock Style="{StaticResource Active}"/>
I Would like to have something like
<TextBlock Style="{StaticResource {Binding ExecutionState}}"/>
Thus if the state changes the text color changes.
Is something like this even possible?
I can achieve the wanted functionality using triggers, but I have to reuse it at several places and I don't want to clutter my code.
I am using MVVM also.
thanx
No, it's not possible. Binding can only be set on a DependencyProperty. StaticResource is not a DependencyObject, so there is no DependencyProperty. You should use Trigger(s) or develop your own attached behavior.
There is no direct way to achieve .
Create one attached property and assign the property name to bind.
In the property change callback function update control style.
<TextBlock dep:CustomStyle.StyleName="{Binding ExecutionState}" Text="Thiru" />
public static class CustomStyle
{
static FrameworkPropertyMetadata _styleMetadata = new FrameworkPropertyMetadata(
string.Empty, FrameworkPropertyMetadataOptions.AffectsRender, StyleNamePropertyChangeCallBack);
public static readonly DependencyProperty StyleNameProperty =
DependencyProperty.RegisterAttached("StyleName", typeof (String), typeof (CustomStyle), _styleMetadata);
public static void SetStyleName(UIElement element, string value)
{
element.SetValue(StyleNameProperty, value);
}
public static Boolean GetStyleName(UIElement element)
{
return (Boolean)element.GetValue(StyleNameProperty);
}
public static void StyleNamePropertyChangeCallBack(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
FrameworkElement ctrl = d as FrameworkElement;
if (ctrl.IsLoaded)
{
string styleName = Convert.ToString(e.NewValue);
if (!string.IsNullOrEmpty(styleName))
{
ctrl.Style = ctrl.TryFindResource(styleName) as Style;
}
}
}
}

WPF does not seem to find a custom attached property

I am trying to bind a property (Button.Background) to a property on my custom attached property.
In a C# file I have
public static class Square
{
public static readonly DependencyProperty PlayerProperty =
DependencyProperty.RegisterAttached
(
name : "Player",
propertyType : typeof(Player),
ownerType : typeof(UIElement),
defaultMetadata: new FrameworkPropertyMetadata(null)
);
public static Player GetPlayer(UIElement element)
{
return (Player)element.GetValue(PlayerProperty);
}
public static void SetPlayer(UIElement element, Player player)
{
element.SetValue(PlayerProperty, player);
}
// Other attached properties
}
A snippet of my XAML is
<Grid Name="board" Grid.Row="1" Grid.Column="1">
<Grid.Resources>
<Style TargetType="{x:Type Button}">
<Setter Property="Height" Value="20" />
<Setter Property="Width" Value="20" />
<Setter Property="BorderThickness" Value="3" />
<Setter Property="Background"
Value="{Binding Path=(l:Square.Player).Brush, Mode=OneWay}" />
</Style>
</Grid.Resources>
</Grid>
This is the error I get:
Cannot convert string '(l:Square.Player).Brush' in attribute 'Path' to object of type 'System.Windows.PropertyPath'.
Property path is not valid. 'Square' does not have a public property named 'Player'.
Error at object 'System.Windows.Data.Binding' in markup file 'Gobang.Gui;component/mainwindow.xaml' Line 148 Position 59.
But since Player is an attached property which is on Square, the above code should work, right?
I believe your attached property should designate Square as the owner rather than UIElement.
public static readonly DependencyProperty PlayerProperty =
DependencyProperty.RegisterAttached("Player", typeof(Player),
typeof(Square), new FrameworkPropertyMetadata(null));
I got it to work.
Note: its a read-only property, the Helper class HAS TO inherit from DependencyObject
public class Helper : DependencyObject
{
public static readonly DependencyPropertyKey IsExpandedKey = DependencyProperty.RegisterAttachedReadOnly(
"IsExpanded", typeof(bool), typeof(Helper), new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.Inherits));
public static readonly DependencyProperty IsExpandedProperty = IsExpandedKey.DependencyProperty;
public static bool GetIsExpanded(DependencyObject d)
{
return (bool)d.GetValue(IsExpandedKey.DependencyProperty);
}
internal static void SetIsExpanded(DependencyObject d, bool value)
{
d.SetValue(IsExpandedKey, value);
}
}
You can't set up a binding in the way that you're doing it - you'll need an instance of either Square or Player to bind to that.

Resources