Related
In WPF, how would I apply multiple styles to a FrameworkElement? For instance, I have a control which already has a style. I also have a separate style which I would like to add to it without blowing away the first one. The styles have different TargetTypes, so I can't just extend one with the other.
I think the simple answer is that you can't do (at least in this version of WPF) what you are trying to do.
That is, for any particular element only one Style can be applied.
However, as others have stated above, maybe you can use BasedOn to help you out. Check out the following piece of loose xaml. In it you will see that I have a base style that is setting a property that exists on the base class of the element that I want to apply two styles to. And, in the second style which is based on the base style, I set another property.
So, the idea here ... is if you can somehow separate the properties that you want to set ... according the inheritance hierarchy of the element you want to set multiple styles on ... you might have a workaround.
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Page.Resources>
<Style x:Key="baseStyle" TargetType="FrameworkElement">
<Setter Property="HorizontalAlignment" Value="Left"/>
</Style>
<Style TargetType="Button" BasedOn="{StaticResource baseStyle}">
<Setter Property="Content" Value="Hello World"/>
</Style>
</Page.Resources>
<Grid>
<Button Width="200" Height="50"/>
</Grid>
</Page>
Note:
One thing in particular to note. If you change the TargetType in the second style (in first set of xaml above) to ButtonBase, the two Styles do not get applied. However, check out the following xaml below to get around that restriction. Basically, it means you need to give the Style a key and reference it with that key.
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Page.Resources>
<Style x:Key="baseStyle" TargetType="FrameworkElement">
<Setter Property="HorizontalAlignment" Value="Left"/>
</Style>
<Style x:Key="derivedStyle" TargetType="ButtonBase" BasedOn="{StaticResource baseStyle}">
<Setter Property="Content" Value="Hello World"/>
</Style>
</Page.Resources>
<Grid>
<Button Width="200" Height="50" Style="{StaticResource derivedStyle}"/>
</Grid>
</Page>
Bea Stollnitz had a good blog post about using a markup extension for this, under the heading "How can I set multiple styles in WPF?"
That blog is dead now, so I'm reproducing the post here:
WPF and Silverlight both offer the ability to derive a Style from
another Style through the “BasedOn” property. This feature enables
developers to organize their styles using a hierarchy similar to class
inheritance. Consider the following styles:
<Style TargetType="Button" x:Key="BaseButtonStyle">
<Setter Property="Margin" Value="10" />
</Style>
<Style TargetType="Button" x:Key="RedButtonStyle" BasedOn="{StaticResource BaseButtonStyle}">
<Setter Property="Foreground" Value="Red" />
</Style>
With this syntax, a Button that uses RedButtonStyle will have its
Foreground property set to Red and its Margin property set to 10.
This feature has been around in WPF for a long time, and it’s new in
Silverlight 3.
What if you want to set more than one style on an element? Neither WPF
nor Silverlight provide a solution for this problem out of the box.
Fortunately there are ways to implement this behavior in WPF, which I
will discuss in this blog post.
WPF and Silverlight use markup extensions to provide properties with
values that require some logic to obtain. Markup extensions are easily
recognizable by the presence of curly brackets surrounding them in
XAML. For example, the {Binding} markup extension contains logic to
fetch a value from a data source and update it when changes occur; the
{StaticResource} markup extension contains logic to grab a value from
a resource dictionary based on a key. Fortunately for us, WPF allows
users to write their own custom markup extensions. This feature is not
yet present in Silverlight, so the solution in this blog is only
applicable to WPF.
Others
have written great solutions to merge two styles using markup
extensions. However, I wanted a solution that provided the ability to
merge an unlimited number of styles, which is a little bit trickier.
Writing a markup extension is straightforward. The first step is to
create a class that derives from MarkupExtension, and use the
MarkupExtensionReturnType attribute to indicate that you intend the
value returned from your markup extension to be of type Style.
[MarkupExtensionReturnType(typeof(Style))]
public class MultiStyleExtension : MarkupExtension
{
}
Specifying inputs to the markup extension
We’d like to give users of our markup extension a simple way to
specify the styles to be merged. There are essentially two ways in
which the user can specify inputs to a markup extension. The user can
set properties or pass parameters to the constructor. Since in this
scenario the user needs the ability to specify an unlimited number of
styles, my first approach was to create a constructor that takes any
number of strings using the “params” keyword:
public MultiStyleExtension(params string[] inputResourceKeys)
{
}
My goal was to be able to write the inputs as follows:
<Button Style="{local:MultiStyle BigButtonStyle, GreenButtonStyle}" … />
Notice the comma separating the different style keys. Unfortunately,
custom markup extensions don’t support an unlimited number of
constructor parameters, so this approach results in a compile error.
If I knew in advance how many styles I wanted to merge, I could have
used the same XAML syntax with a constructor taking the desired number
of strings:
public MultiStyleExtension(string inputResourceKey1, string inputResourceKey2)
{
}
As a workaround, I decided to have the constructor parameter take a
single string that specifies the style names separated by spaces. The
syntax isn’t too bad:
<Button Style="{local:MultiStyle BigButtonStyle GreenButtonStyle}" … />
private string[] resourceKeys;
public MultiStyleExtension(string inputResourceKeys)
{
if (inputResourceKeys == null)
{
throw new ArgumentNullException("inputResourceKeys");
}
this.resourceKeys = inputResourceKeys.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
if (this.resourceKeys.Length == 0)
{
throw new ArgumentException("No input resource keys specified.");
}
}
Calculating the output of the markup extension
To calculate the output of a markup extension, we need to override a
method from MarkupExtension called “ProvideValue”. The value returned
from this method will be set in the target of the markup extension.
I started by creating an extension method for Style that knows how to
merge two styles. The code for this method is quite simple:
public static void Merge(this Style style1, Style style2)
{
if (style1 == null)
{
throw new ArgumentNullException("style1");
}
if (style2 == null)
{
throw new ArgumentNullException("style2");
}
if (style1.TargetType.IsAssignableFrom(style2.TargetType))
{
style1.TargetType = style2.TargetType;
}
if (style2.BasedOn != null)
{
Merge(style1, style2.BasedOn);
}
foreach (SetterBase currentSetter in style2.Setters)
{
style1.Setters.Add(currentSetter);
}
foreach (TriggerBase currentTrigger in style2.Triggers)
{
style1.Triggers.Add(currentTrigger);
}
// This code is only needed when using DynamicResources.
foreach (object key in style2.Resources.Keys)
{
style1.Resources[key] = style2.Resources[key];
}
}
With the logic above, the first style is modified to include all
information from the second. If there are conflicts (e.g. both styles
have a setter for the same property), the second style wins. Notice
that aside from copying styles and triggers, I also took into account
the TargetType and BasedOn values as well as any resources the second
style may have. For the TargetType of the merged style, I used
whichever type is more derived. If the second style has a BasedOn
style, I merge its hierarchy of styles recursively. If it has
resources, I copy them over to the first style. If those resources are
referred to using {StaticResource}, they’re statically resolved before
this merge code executes, and therefore it isn’t necessary to move
them. I added this code in case we’re using DynamicResources.
The extension method shown above enables the following syntax:
style1.Merge(style2);
This syntax is useful provided that I have instances of both styles
within ProvideValue. Well, I don’t. All I get from the constructor is
a list of string keys for those styles. If there was support for
params in the constructor parameters, I could have used the following
syntax to get the actual style instances:
<Button Style="{local:MultiStyle {StaticResource BigButtonStyle}, {StaticResource GreenButtonStyle}}" … />
public MultiStyleExtension(params Style[] styles)
{
}
But that doesn’t work. And even if the params limitation didn’t exist,
we would probably hit another limitation of markup extensions, where
we would have to use property-element syntax instead of attribute
syntax to specify the static resources, which is verbose and
cumbersome (I explain this bug better in a previous blog
post).
And even if both those limitations didn’t exist, I would still rather
write the list of styles using just their names – it is shorter and
simpler to read than a StaticResource for each one.
The solution is to create a StaticResourceExtension using code. Given
a style key of type string and a service provider, I can use
StaticResourceExtension to retrieve the actual style instance. Here is
the syntax:
Style currentStyle = new StaticResourceExtension(currentResourceKey).ProvideValue(serviceProvider) as Style;
Now we have all the pieces needed to write the ProvideValue method:
public override object ProvideValue(IServiceProvider serviceProvider)
{
Style resultStyle = new Style();
foreach (string currentResourceKey in resourceKeys)
{
Style currentStyle = new StaticResourceExtension(currentResourceKey).ProvideValue(serviceProvider) as Style;
if (currentStyle == null)
{
throw new InvalidOperationException("Could not find style with resource key " + currentResourceKey + ".");
}
resultStyle.Merge(currentStyle);
}
return resultStyle;
}
Here is a complete example of the usage of the MultiStyle markup
extension:
<Window.Resources>
<Style TargetType="Button" x:Key="SmallButtonStyle">
<Setter Property="Width" Value="120" />
<Setter Property="Height" Value="25" />
<Setter Property="FontSize" Value="12" />
</Style>
<Style TargetType="Button" x:Key="GreenButtonStyle">
<Setter Property="Foreground" Value="Green" />
</Style>
<Style TargetType="Button" x:Key="BoldButtonStyle">
<Setter Property="FontWeight" Value="Bold" />
</Style>
</Window.Resources>
<Button Style="{local:MultiStyle SmallButtonStyle GreenButtonStyle BoldButtonStyle}" Content="Small, green, bold" />
But you can extend from another.. take a look at the BasedOn property
<Style TargetType="TextBlock">
<Setter Property="Margin" Value="3" />
</Style>
<Style x:Key="AlwaysVerticalStyle" TargetType="TextBlock"
BasedOn="{StaticResource {x:Type TextBlock}}">
<Setter Property="VerticalAlignment" Value="Top" />
</Style>
WPF/XAML doesn't provide this functionality natively, but it does provide the extensibility to allow you to do what you want.
We ran into the same need, and ended up creating our own XAML Markup Extension (which we called "MergedStylesExtension") to allow us to create a new Style from two other styles (which, if needed, could probably be used multiple times in a row to inherit from even more styles).
Due to a WPF/XAML bug, we need to use property element syntax to use it, but other than that it seems to work ok. E.g.,
<Button
Content="This is an example of a button using two merged styles">
<Button.Style>
<ext:MergedStyles
BasedOn="{StaticResource FirstStyle}"
MergeStyle="{StaticResource SecondStyle}"/>
</Button.Style>
</Button>
I recently wrote about it here:
http://swdeveloper.wordpress.com/2009/01/03/wpf-xaml-multiple-style-inheritance-and-markup-extensions/
This is possible by creating a helper class to use and wrap your styles. CompoundStyle mentioned here shows how to do it. There are multiple ways, but the easiest is to do the following:
<TextBlock Text="Test"
local:CompoundStyle.StyleKeys="headerStyle,textForMessageStyle,centeredStyle"/>
Hope that helps.
Use AttachedProperty to set multiple styles like following code:
public static class Css
{
public static string GetClass(DependencyObject element)
{
if (element == null)
throw new ArgumentNullException("element");
return (string)element.GetValue(ClassProperty);
}
public static void SetClass(DependencyObject element, string value)
{
if (element == null)
throw new ArgumentNullException("element");
element.SetValue(ClassProperty, value);
}
public static readonly DependencyProperty ClassProperty =
DependencyProperty.RegisterAttached("Class", typeof(string), typeof(Css),
new PropertyMetadata(null, OnClassChanged));
private static void OnClassChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var ui = d as FrameworkElement;
Style newStyle = new Style();
if (e.NewValue != null)
{
var names = e.NewValue as string;
var arr = names.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var name in arr)
{
Style style = ui.FindResource(name) as Style;
foreach (var setter in style.Setters)
{
newStyle.Setters.Add(setter);
}
foreach (var trigger in style.Triggers)
{
newStyle.Triggers.Add(trigger);
}
}
}
ui.Style = newStyle;
}
}
Usage: (Point the xmlns:local="clr-namespace:style_a_class_like_css" to the right namespace)
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:style_a_class_like_css"
mc:Ignorable="d"
Title="MainWindow" Height="150" Width="325">
<Window.Resources>
<Style TargetType="TextBlock" x:Key="Red" >
<Setter Property="Foreground" Value="Red"/>
</Style>
<Style TargetType="TextBlock" x:Key="Green" >
<Setter Property="Foreground" Value="Green"/>
</Style>
<Style TargetType="TextBlock" x:Key="Size18" >
<Setter Property="FontSize" Value="18"/>
<Setter Property="Margin" Value="6"/>
</Style>
<Style TargetType="TextBlock" x:Key="Bold" >
<Setter Property="FontWeight" Value="Bold"/>
</Style>
</Window.Resources>
<StackPanel>
<Button Content="Button" local:Css.Class="Red Bold" Width="75"/>
<Button Content="Button" local:Css.Class="Red Size18" Width="75"/>
<Button Content="Button" local:Css.Class="Green Size18 Bold" Width="75"/>
</StackPanel>
</Window>
Result:
if you are not touching any specific properties, you can get all base and common properties to the style which's target type would be FrameworkElement. then, you can create specific flavours for each target types you need, without need of copying all those common properties again.
You can probably get something similar if applying this to a collection of items by the use of a StyleSelector, i have used this to approach a similar problem in using different styles on TreeViewItems depending on the bound object type in the tree. You may have to modify the class below slightly to adjust to your particular approach but hopefully this will get you started
public class MyTreeStyleSelector : StyleSelector
{
public Style DefaultStyle
{
get;
set;
}
public Style NewStyle
{
get;
set;
}
public override Style SelectStyle(object item, DependencyObject container)
{
ItemsControl ctrl = ItemsControl.ItemsControlFromItemContainer(container);
//apply to only the first element in the container (new node)
if (item == ctrl.Items[0])
{
return NewStyle;
}
else
{
//otherwise use the default style
return DefaultStyle;
}
}
}
You then apply this as so
<TreeView>
<TreeView.ItemContainerStyleSelector
<myassembly:MyTreeStyleSelector DefaultStyle="{StaticResource DefaultItemStyle}"
NewStyle="{StaticResource NewItemStyle}" />
</TreeView.ItemContainerStyleSelector>
</TreeView>
Sometimes you can approach this by nesting panels. Say you have a Style which changes Foreground and another changes FontSize, you can apply the latter one on a TextBlock, and put it in a Grid which its Style is the first one. This might help and might be the easiest way in some cases, though it won't solve all the problems.
When you override SelectStyle you can get GroupBy property via reflection like below:
public override Style SelectStyle(object item, DependencyObject container)
{
PropertyInfo p = item.GetType().GetProperty("GroupBy", BindingFlags.NonPublic | BindingFlags.Instance);
PropertyGroupDescription propertyGroupDescription = (PropertyGroupDescription)p.GetValue(item);
if (propertyGroupDescription != null && propertyGroupDescription.PropertyName == "Title" )
{
return this.TitleStyle;
}
if (propertyGroupDescription != null && propertyGroupDescription.PropertyName == "Date")
{
return this.DateStyle;
}
return null;
}
If you are trying to apply a unique style to just one single element as an addition to a base style, there is a completely different way to do this that is IMHO much better for readable and maintainable code.
It's extremely common to need to tweak parameters per individual element. Defining dictionary styles just for use on one-element is extremely cumbersome to maintain or make sense of. To avoid creating styles just for one-off element tweaks, read my answer to my own question here here:
https://stackoverflow.com/a/54497665/1402498
Seems like this should be fairly simple, as it is in Winforms, but I'm relatively new to WPF so still trying to change thinking how data and UI interact.
Scenario: User clicks a button on my main form. The button is used to enter a street address. In the street address form, I do some basic data validation when the user clicks a submit button. Submit() iterates through each of the data entry fields and calls the method below to attempt to alert the user to the offending data field.
Here's the code I have that doesn't do anything that I can detect:
private void FlashTextBox(RichTextBox box)
{
var currentBorderColor = box.BorderBrush;
var currentBackgroundColor = box.Background;
Task.Factory.StartNew(() =>
{
for (int x = 0; x < 5; x++)
{
this.Dispatcher.Invoke(() =>
{
box.Background = Brushes.Red;
box.BorderBrush = Brushes.IndianRed;
box.InvalidateVisual();
System.Threading.Thread.Sleep(100);
box.BorderBrush = currentBorderColor;
box.Background = currentBackgroundColor;
box.InvalidateVisual();
System.Threading.Thread.Sleep(100);
});
}
});
}
As I've noted in my comment, the primary issue with your code is that you have blocked the UI thread. So, while you are changing the properties of interest to new values in a loop, the actual UI never gets a chance to update the visual representation, i.e. what's on the screen.
Ironically, while you note "Seems like this should be fairly simple, as it is in Winforms", had you tried to write the same code in a Winforms program, you would have had the exact same problem. Both Winforms and WPF (and indeed, most GUI APIs) have exactly this same limitation: there's one thread that handles all of the UI, and after you change one or more data values that should affect how the UI looks, you have to return control to the UI thread that called you, so that it can then update the screen.
Now, you note also that you are "trying to change thinking how data and UI interact". This is a good thing, and if you are willing to take the time to learn the MVVM concepts WPF was designed to work with, that will help a lot. Winforms also has a data binding model, and you can in fact write very similar code in Winforms as is strongly encouraged for WPF. But, WPF's "retained" graphics model as opposed to Winform's "immediate" model — i.e. WPF keeps track of what your graphics are supposed to look like, while Winform demands that you handle drawing the graphics yourself every time the screen needs updating — lends itself much better to the data binding approach, and WPF's entire design is based on that.
This means that you should work hard to keep your data where the data goes, and your UI where the UI goes. I.e. data is in your code-behind, and UI is in the XAML. It's a good idea in both APIs, but you're sacrificing a lot more if you fail to do it with WPF.
So where does that leave your question? Well, lacking a good minimal, complete, and verifiable code example, it's hard to know what your code looks like, and so what would be the best way to fix it. So instead, I'll provide a couple of examples in the hopes that after you reorient your code to fit the WPF paradigm better, you can apply one as you see fit. (Unfortunately, one of the things I don't like much about WPF is that in some ways it's too powerful, offering many different ways to accomplish the same result; this can make it really hard sometimes to know what is the best way.)
These two examples differ from each other in how much code-behind they require. The first puts the animation logic into the C# code, as part of the view model. On the one hand, this is arguably less "the WPF way". But the second, which uses the view code (i.e. the XAML) to define the animation, requires a tiny bit of extra plumbing in the view's code-behind, which bugs me a little, as it blurs the line between view and view model a bit more than I'd like.
Oh well.
Here's the view model class for the first approach:
class ViewModel : NotifyPropertyChangedBase
{
private string _text;
public string Text
{
get => _text;
set => _UpdateField(ref _text, value);
}
private bool _isHighlighted;
public bool IsHighlighted
{
get => _isHighlighted;
set => _UpdateField(ref _isHighlighted, value);
}
private bool _isAnimating;
public bool IsAnimating
{
get => _isAnimating;
set => _UpdateField(ref _isAnimating, value, _OnIsAnimatingChanged);
}
private void _OnIsAnimatingChanged(bool oldValue)
{
_toggleIsHighlightedCommand.RaiseCanExecuteChanged();
_animateIsHighlightedCommand.RaiseCanExecuteChanged();
}
private readonly DelegateCommand _toggleIsHighlightedCommand;
private readonly DelegateCommand _animateIsHighlightedCommand;
public ICommand ToggleIsHighlightedCommand => _toggleIsHighlightedCommand;
public ICommand AnimateIsHighlightedCommand => _animateIsHighlightedCommand;
public ViewModel()
{
_toggleIsHighlightedCommand = new DelegateCommand(() => IsHighlighted = !IsHighlighted, () => !IsAnimating);
_animateIsHighlightedCommand = new DelegateCommand(() => _FlashIsHighlighted(this), () => !IsAnimating);
}
private static async void _FlashIsHighlighted(ViewModel viewModel)
{
viewModel.IsAnimating = true;
for (int i = 0; i < 10; i++)
{
viewModel.IsHighlighted = !viewModel.IsHighlighted;
await Task.Delay(200);
}
viewModel.IsAnimating = false;
}
}
class NotifyPropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void _UpdateField<T>(ref T field, T newValue,
Action<T> onChangedCallback = null,
[CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, newValue))
{
return;
}
T oldValue = field;
field = newValue;
onChangedCallback?.Invoke(oldValue);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
class DelegateCommand : ICommand
{
private readonly Action _execute;
private readonly Func<bool> _canExecute;
public DelegateCommand(Action execute, Func<bool> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public DelegateCommand(Action execute) : this(execute, null) { }
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter) => _canExecute?.Invoke() != false;
public void Execute(object parameter) => _execute?.Invoke();
public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
The second class there, NotifyPropertyChangedBase, is just my standard base class for my view models. It contains all the boilerplate to support the INotifyPropertyChanged interface. There are WPF frameworks that include such a base class themselves; why WPF doesn't itself provide one, I don't know. But it's handy to have, and between it and a Visual Studio code snippet to paste in the property template, it makes it a lot faster to put together the view models for a program.
Similarly, the third class, DelegateCommand, makes it easier to define ICommand objects. Again, this type of class is available in third-party WPF frameworks as well. (I also have a version of the class that is generic with the type parameter specifying the type of the command parameter passed to the CanExecute() and Execute() methods, but since we don't need that here, I didn't bother to include it.
As you can see, once you get past the boilerplate, the code's pretty simple. It has a pro-forma Text property just so I have something to bind to the TextBox in my UI. It also has a couple of bool properties that relate to the visual state of the TextBox. One determines the actual visual state, while the other provides some indication as to whether that state is currently being animated.
There are two ICommand instances providing user interaction with the view model. One just toggles the visual state, while the other causes the animation you want to happen.
Finally, there's the method that actually does the work. It first sets the IsAnimating property, then loops ten times to toggle the IsHighlighted property. This method uses async. In a Winforms program, this would be essential, so that the UI property updates happened in the UI thread. But in this WPF program, it's optional. I like the async/await programming model, but for simple property-change notifications, WPF will marshal the binding update back to the UI thread as necessary, so you could in fact just create a background task in the thread pool or a dedicated thread to handle the animation.
(For the animation, I used 200 ms between frames instead of 100 as your code would've, just because I think it looks better, and in any case makes it easier to see what the animation is doing.)
Note that the view model itself has no idea there's a UI involved per se. It just has a property that indicates whether the text box should be highlighted or not. It's up to the UI to figure out how to do that.
And that, looks like this:
<Window x:Class="TestSO57403045FlashBorderBackground.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:p="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:l="clr-namespace:TestSO57403045FlashBorderBackground"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<l:ViewModel/>
</Window.DataContext>
<StackPanel>
<Button Command="{Binding ToggleIsHighlightedCommand}" Content="Toggle Control" HorizontalAlignment="Left"/>
<Button Command="{Binding AnimateIsHighlightedCommand}" Content="Flash Control" HorizontalAlignment="Left"/>
<TextBox x:Name="textBox1" Width="100" Text="{Binding Text}" HorizontalAlignment="Left">
<TextBox.Style>
<p:Style TargetType="TextBox">
<Setter Property="BorderBrush" Value="Black"/>
<Setter Property="BorderThickness" Value="2"/>
<Setter Property="Background" Value="WhiteSmoke"/>
<p:Style.Triggers>
<DataTrigger Binding="{Binding IsHighlighted}" Value="True">
<Setter Property="BorderBrush" Value="IndianRed"/>
<Setter Property="Background" Value="Red"/>
</DataTrigger>
</p:Style.Triggers>
</p:Style>
</TextBox.Style>
</TextBox>
</StackPanel>
</Window>
This just sets some default values for the border and background colors. And then, importantly, it defines a data trigger that will temporarily override these defaults any time the condition in the data trigger is true. That is, the declared binding evaluates to the declared value given (which in my example above is in fact the bool value of true).
Every place you see an element property set to something that looks like {Binding}, that's a reference back to the current data context, which in this case is set to my view model class.
Now, WPF has a very rich animation feature set, and that can be used instead of the above to handle the flashing animation. If we're going to do it that way, then the view model can be simpler, as we don't need the explicit property for the highlighted state. We do still need the IsAnimating property, but this time instead of the "animate" command calling a method, which sets this property as a side-effect, the command sets the property directly and does nothing else (and that property, now the primary controller for the animation, still does serve as the flag so that the button's command can be enabled/disabled as needed):
class ViewModel : NotifyPropertyChangedBase
{
private string _text;
public string Text
{
get => _text;
set => _UpdateField(ref _text, value);
}
private bool _isAnimating;
public bool IsAnimating
{
get => _isAnimating;
set => _UpdateField(ref _isAnimating, value, _OnIsAnimatingChanged);
}
private void _OnIsAnimatingChanged(bool oldValue)
{
_animateIsHighlightedCommand.RaiseCanExecuteChanged();
}
private readonly DelegateCommand _animateIsHighlightedCommand;
public ICommand AnimateIsHighlightedCommand => _animateIsHighlightedCommand;
public ViewModel()
{
_animateIsHighlightedCommand = new DelegateCommand(() => IsAnimating = true, () => !IsAnimating);
}
}
Importantly, you'll notice that now the view model doesn't contain any code to actually run the animation. That, we'll find in the XAML:
<Window x:Class="TestSO57403045FlashBorderBackground.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:p="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:l="clr-namespace:TestSO57403045FlashBorderBackground"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<l:ViewModel/>
</Window.DataContext>
<Window.Resources>
<Storyboard x:Key="flashBorder" RepeatBehavior="5x"
Completed="flashStoryboard_Completed">
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Background).(SolidColorBrush.Color)"
Duration="0:0:0.4">
<DiscreteColorKeyFrame KeyTime="0:0:0" Value="IndianRed"/>
<DiscreteColorKeyFrame KeyTime="0:0:0.2" Value="WhiteSmoke"/>
</ColorAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(BorderBrush).(SolidColorBrush.Color)"
Duration="0:0:0.4">
<DiscreteColorKeyFrame KeyTime="0:0:0" Value="Red"/>
<DiscreteColorKeyFrame KeyTime="0:0:0.2" Value="Black"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</Window.Resources>
<StackPanel>
<Button Command="{Binding AnimateIsHighlightedCommand}" Content="Flash Control" HorizontalAlignment="Left"/>
<TextBox x:Name="textBox1" Width="100" Text="{Binding Text}" HorizontalAlignment="Left">
<TextBox.Style>
<p:Style TargetType="TextBox">
<Setter Property="BorderBrush" Value="Black"/>
<Setter Property="BorderThickness" Value="2"/>
<Setter Property="Background" Value="WhiteSmoke"/>
<p:Style.Triggers>
<DataTrigger Binding="{Binding IsAnimating}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource flashBorder}" Name="flashBorderBegin"/>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<StopStoryboard BeginStoryboardName="flashBorderBegin"/>
</DataTrigger.ExitActions>
</DataTrigger>
</p:Style.Triggers>
</p:Style>
</TextBox.Style>
</TextBox>
</StackPanel>
</Window>
In this case, there's a Storyboard object which contains two animation sequences (both are started simultaneously) that do the actual flashing of the control. The storyboard itself lets you specify how many times it should repeat ("5x" in this case, for five times), and then within each animation sequence, the duration of the whole sequence (400 ms, since one sequence involves two states, each displayed for 200 ms), and then the "key frames" that dictate what actually happens during the animation, each specifying at what time during the animation it should take effect.
Then, in the text box's style, instead of triggering property setters, the storyboard is started and stopped according to the trigger state (entered or exited).
Note that in the storyboard, the Completed event is subscribed to. Whereas in the previous example, there was no change to the default MainWindow.xaml.cs file, for this version there's a little bit of code:
public partial class MainWindow : Window
{
private readonly ViewModel _viewModel;
public MainWindow()
{
InitializeComponent();
_viewModel = (ViewModel)DataContext;
}
private void flashStoryboard_Completed(object sender, EventArgs e)
{
_viewModel.IsAnimating = false;
}
}
This has the implementation of the event handler for the Storyboard.Completed event. And since that handler is going to need to modify the view model state, there is now code to retrieve the view model from the DataContext property and save it in a field so that the event handler can get at it.
This event handler is what allows the IsAnimating property to be set back to false once the animation has completed.
So, there you go. It is possible that there's a better way to do this, but I think these two examples should give you a good place to start in terms of seeing how things "ought to be done" in WPF.
(I'll admit, the one thing that really bugs me about the animation approach is that I'd rather not have to explicitly state in the storyboard the original colors for the text box; but I'm not aware of any way to specify a key frame in the <ColorAnimationUsingKeyFrame/> element that instead of actually setting a new color, just removes whatever changes the animation had already applied.)
I have a wpf Custom Control on which I have been working. It has a shared New like this:
Shared Sub New()
'This OverrideMetadata call tells the system that this element wants to provide a style that is different than its base class.
'This style is defined in themes\generic.xaml
DefaultStyleKeyProperty.OverrideMetadata(GetType(VtlDataNavigator_24), New FrameworkPropertyMetadata(GetType(VtlDataNavigator_24)))
ItemsSourceProperty.OverrideMetadata(GetType(VtlDataNavigator_24), New FrameworkPropertyMetadata(Nothing, AddressOf OnItemsSourceHasChanged))
End Sub
If an Items source has been set for the custom control this shared sub then invokes the overrideMetadata for the itemssource (as shown below)
Private Shared Sub OnItemsSourceHasChanged(ByVal d As DependencyObject, ByVal baseValue As Object)
Dim vdn As VtlDataNavigator_24 = DirectCast(d, VtlDataNavigator_24)
vdn.RecordCount = vdn.Items.SourceCollection.Cast(Of Object)().Count()
vdn.MyBaseCollection = DirectCast(vdn.ItemsSource, ICollectionView)
vdn.MyBaseEditableCollection = DirectCast(vdn.ItemsSource, IEditableCollectionView)
vdn.MyBaseCollection.MoveCurrentToFirst
vdn.RecordIndex = vdn.MyBaseCollection.CurrentPosition + 1
If Not IsNothing(vdn.FindButton) Then
If vdn.FindButton.Visibility = Visibility.Visible Then
vdn.RecordIndexTextBox.IsReadOnly = False
Else
vdn.RecordIndexTextBox.IsReadOnly = True
End If
End If
vdn.ResetTheNavigationButtons
vdn.SetupInitialStatesForNonNavigationButtons
End Sub
This then fails because buttons referred to in the code (and routines called from it) have not yet been instantiated because the override for OnApplyTemplate (shown below) has not been called.
Public Overrides Sub OnApplyTemplate()
MyBase.OnApplyTemplate()
RecordIndexTextBox = CType(GetTemplateChild("PART_RecordIndexTextBox"), TextBox)
RecordCountTextBox = CType(GetTemplateChild(RecordCountTextBoxPart), TextBox)
RecordTextBlock = CType(GetTemplateChild(RecordTextBlockPart), TextBlock)
OfTextBlock = CType(GetTemplateChild(OfTextBlockPart), TextBlock)
FirstButton = CType(GetTemplateChild(FirstButtonPart), Button)
PreviousButton = CType(GetTemplateChild(PreviousButtonPart), RepeatButton)
NextButton = CType(GetTemplateChild(NextButtonPart), RepeatButton)
LastButton = CType(GetTemplateChild(LastButtonPart), Button)
AddButton = CType(GetTemplateChild(AddButtonPart), Button)
CancelNewRecordButton = CType(GetTemplateChild(CancelNewButtonPart), Button)
EditButton = CType(GetTemplateChild(EditButtonPart), button)
CancelButton = CType(GetTemplateChild(CancelButtonPart), Button)
RefreshButton = CType(GetTemplateChild(RefreshButtonPart), Button)
SaveButton = CType(GetTemplateChild(SaveButtonPart), Button)
DeleteButton = CType(GetTemplateChild(DeleteButtonPart), Button)
FindButton = CType(GetTemplateChild(FindButtonPart), Button)
End Sub
If I add something along the lines of:
vdn.OnApplyTemplate
to OnItemsSourceHasChanged, OnApplyTemplate is called but nothing is resolved (see illustration below).
BUT if I don't set an itemssource on my control, then OnApplyTemplate gets called and the items resolve (see below)
Has anyone encountered this sort of behaviour before and found a way to correct it such that OnApplyTemplate is always the first thing to get called before anything that might require access to controls that have yet to be resolved.
Edit
The curious thing about this issue is that (and doesn't this always seem to be the case!) this was working until obviously I did something or set some property. What I am left with is a project that runs if I do not set an Items source on my custom control, and one which doesn't if I do because the custom handler I have in place to handle when the items source is changed on my custom control is running before OnApplyTemplate gets called.
Well I have at last been able to determine that my custom controls Itemssource property is being changed before the control is being drawn and rendered and therefore the code I have in place to set things up following the ItemsSource change raises null reference exceptions because the main control has yet to be rendered.
Given that it did work it must be something I've done but I'm now out od ideas as to how to delve into this further and actually find the reason. I'd welcome any suggestions you might have or potential work rounds.
Edit in relation to comments below: typical part of control template.
<!-- First Button -->
<Button Style="{StaticResource vtlNavButtonStyle}"
x:Name="PART_FirstButton"
Tag="First_Button"
Visibility="{Binding Path=NavigationButtonVisibility,Converter={StaticResource booltovis}, RelativeSource={RelativeSource TemplatedParent}}"
ToolTipService.ShowOnDisabled="False"
ToolTipService.ShowDuration="3000"
ToolTipService.InitialShowDelay="500">
<Button.ToolTip>
<Binding Path="FirstButtonToolTip"
RelativeSource="{RelativeSource TemplatedParent}"
TargetNullValue="{x:Static p:Resources.FirstText}">
</Binding>
</Button.ToolTip>
<StackPanel>
<Image Style="{StaticResource vtlImageStyle}">
<Image.Source>
<Binding Path="FirstImage"
RelativeSource="{RelativeSource TemplatedParent}">
<Binding.TargetNullValue>
<ImageSource>/VtlWpfControls;component/Images/16/first.png</ImageSource>
</Binding.TargetNullValue>
</Binding>
</Image.Source>
</Image>
</StackPanel>
</Button>
Calling OnApplyTemplate yourself isn't going to help; the framework will call it when the template has actually been applied. That said, the order in which things happen is not deterministic -- the template may or may not be applied before the ItemsSource is set. I'm working with UWP apps for Windows 10, which is a slightly different beast, but we've solved a similar issue doing something like this:
private TextBlock textBlock;
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
// Grab the template controls, e.g.:
textBlock = GetTemplateChild("MyTextBlock") as TextBlock;
InitializeDataContext();
DataContextChanged += (sender, args) => InitializeDataContext();
}
private void InitializeDataContext()
{
ViewModel ViewModel = DataContext as ViewModel;
if (viewModel != null)
{
// Here we know that both conditions are satisfied
textBlock.Text = ViewModel.Name;
}
}
The key is to not start listening for DataContextChanged until the template has been applied. If the data context has already been set, the first call to initializeDataContext takes care of things; if not, the callback takes care of things.
(In your case, replace our data context listening with items source listening, I suppose.)
This isn't an answer to your question, but instead expands on some things you mentioned in the comments.
I really think that it would benefit you to look into WPF commands as they pertain to custom controls. Your data navigator control sounds like it essentially supports a number of actions (go to first/previous/next/last; add; edit; cancel; etc) that you invoke using Button controls in the control template. Rather than looking for the buttons in OnApplyTemplate (at which point you store references to them so that you can presumably hook into their Click event later) you should support commands in your control: the buttons in the template would then bind to these commands.
An example would probably make this a bit clearer. The following is code for a custom control that supports two actions: go-to-first-page, and go-to-last-page. In the static constructor I register two command bindings, one for each action. These work by calling into a helper method that takes the command to "bind" to, plus a pair of delegates that get called when the action is invoked.
The commands I am using here are provided by the WPF framework, and are static properties contained in the static NavigationCommands class. (There are a bunch of other similar classes containing commands, just follow the links in the "See Also" section of that MSDN page).
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace StackOverflow
{
public class TestControl : Control
{
static TestControl()
{
RegisterCommandBinding<TestControl>(NavigationCommands.FirstPage,
x => x.GoToFirstPage());
RegisterCommandBinding<TestControl>(NavigationCommands.LastPage,
x => x.GoToLastPage(), x => x.CanGoToLastPage());
DefaultStyleKeyProperty.OverrideMetadata(typeof(TestControl),
new FrameworkPropertyMetadata(typeof(TestControl)));
}
void GoToFirstPage()
{
Console.WriteLine("first page");
}
void GoToLastPage()
{
Console.WriteLine("last page");
}
bool CanGoToLastPage()
{
return true; // Would put your own logic here obviously
}
public static void RegisterCommandBinding<TControl>(
ICommand command, Action<TControl> execute) where TControl : class
{
RegisterCommandBinding<TControl>(command, execute, target => true);
}
public static void RegisterCommandBinding<TControl>(
ICommand command, Action<TControl> execute, Func<TControl, bool> canExecute)
where TControl : class
{
var commandBinding = new CommandBinding(command,
(target, e) => execute((TControl) target),
(target, e) => e.CanExecute = canExecute((TControl) target));
CommandManager.RegisterClassCommandBinding(typeof(TControl), commandBinding);
}
}
}
The following is the control's default template. As you can see there are simply two Button controls, each one of which binds to the relevant command via its Command property (note this is not a data binding, ie. you're not using the {Binding} markup extension).
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:StackOverflow">
<Style TargetType="{x:Type local:TestControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:TestControl}">
<StackPanel Orientation="Horizontal">
<Button Command="NavigationCommands.FirstPage" Content="First" />
<Button Command="NavigationCommands.LastPage" Content="Last" />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Finally, here's the custom control in a Window. As you click the "First" and "Last" buttons you can see the actions being invoked by watching the relevant text appear in the debug console window.
<Window x:Class="StackOverflow.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:StackOverflow">
<local:TestControl VerticalAlignment="Top" />
</Window>
If you use commands in this way then you should be able to simplify your control's code significantly.
I had a similar issue - a custom control (specifically, a class derived from Control) would show binding errors whenever a new instance of the control was instantiated. This was because the control template was being created before the bindings were setup. Once the bindings took effect, then the control would start to work.
To "fix" this (or work around it anyway) I just added a call to ApplyTemplate() to the control's constructor. So it ends up looking like this:
public CustomControl()
{
InitializeComponent();
ApplyTemplate();
}
Then there were no more binding errors.
I'm trying to use both AvalonDock 2.0 (MVVM-compliant) and Caliburn Micro in my WPF application. All works fine, except for a couple of issues connected with closing document panes or hiding tool panes.
My main viewmodel derives from Conductor<IScreen>.Collection.OneActive and exposes two BindableCollection's of Screen-derived viewmodels for Tools and Documents; the corresponding relevant XAML is like:
<xcad:DockingManager Grid.Row="1"
AnchorablesSource="{Binding Path=Tools}"
DocumentsSource="{Binding Path=Documents}"
ActiveContent="{Binding Path=ActiveItem, Mode=TwoWay}">
<xcad:DockingManager.LayoutItemContainerStyle>
<Style TargetType="{x:Type xcad:LayoutItem}">
<Setter Property="Title" Value="{Binding Model.DisplayName}" />
</Style>
</xcad:DockingManager.LayoutItemContainerStyle>
<xcad:DockingManager.LayoutItemTemplateSelector>
<views:AutobinderTemplateSelector>
<views:AutobinderTemplateSelector.Template>
<DataTemplate>
<ContentControl cal:View.Model="{Binding . }" IsTabStop="False" />
</DataTemplate>
</views:AutobinderTemplateSelector.Template>
</views:AutobinderTemplateSelector>
</xcad:DockingManager.LayoutItemTemplateSelector>
<xcad:LayoutRoot>
<xcad:LayoutPanel Orientation="Horizontal">
<xcad:LayoutAnchorablePane DockHeight="150" DockMinWidth="200">
</xcad:LayoutAnchorablePane>
<xcad:LayoutDocumentPane/>
</xcad:LayoutPanel>
</xcad:LayoutRoot>
</xcad:DockingManager>
The template selector is as simple as that:
public class AutobinderTemplateSelector : DataTemplateSelector
{
public DataTemplate Template { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
return Template;
}
}
1. Closing Documents
The first issue comes when handling document-pane close. AD has its document handling mechanism, which should be synchronized with CM's one. CM is based on a screen conductor; when a screen needs to be closed, the method TryClose is used to close it if possible (i.e. unless a guard method tells the framework that the screen cannot be closed, e.g. because the document is dirty). To let AD play with CM I'm using a workaround similar to that described in Prevent document from closing in DockingManager, where the main view code directly calls this method handling the docking manager closing event: when AD is closing the document, call the underlying VM guard method and cancel if required; if not cancelled, then AD goes on closing thus firing the DocumentClosed event.
To see if this could work, I first created a public TryClose method in the document viewmodel base, essentially duplicating the code in the CM TryClose override, like (IsDirty is a protected virtual method overridden by descendant viewmodels):
public bool CanClose()
{
if (!IsDirty()) return true;
MessageBoxAction prompt = new MessageBoxAction
{ ...prompt message here... };
bool bResult = true;
prompt.Completed += (sender, args) =>
{
bResult = prompt.Result == MessageBoxResult.Yes;
};
prompt.Execute(null);
return bResult;
}
This is the method called by the main view code behind in the handlers for AD document closing and document closed:
private void OnDocumentClosing(object sender, DocumentClosingEventArgs e)
{
DocumentBase doc = e.Document.Content as DocumentBase;
if (doc == null) return;
e.Cancel = !doc.CanClose();
}
private void OnDocumentClosed(object sender, DocumentClosedEventArgs e)
{
DocumentBase editor = e.Document.Content as DocumentBase;
if (doc != null) doc.TryClose();
}
Note that I cannot directly call TryClose in OnDocumentClosing, as this would cause null object reference errors in AD. This is really ugly but it works. I can now close documents and my guard methods are called appropriately before proceeding. Anyway, it would be nice to get suggestions for a less hacky solution here.
2. Hiding Tools
Another issue arises from hiding the tools panes. In this case, AD should just hide them. The AD control visibility can be bound to an IsVisible boolean property in my viewmodels implementing tool panes, using a BooleanToVisibility converter. To this end I just add the binding in the XAML:
<xcad:DockingManager.LayoutItemContainerStyle>
<Style TargetType="{x:Type xcad:LayoutItem}">
...
<Setter Property="Visibility"
Value="{Binding Model.IsVisible, Mode=TwoWay, Converter={StaticResource BooleanToVisibilityCvt}}"/>
...
Now, if I hide a tool pane by clicking on its X button, I can see that my VM IsVisible property is set to false as expected, and the pane is hidden. Then, if I programmatically set this property back to true the pane is not shown. Even restoring the layout does not work: I can see that when the application starts and the object corresponding to the hidden VM is being added to the Tools collection its IsVisible is already false. To have it back, I must set this to true and then restore the layout. If I miss any of the two steps, the pane remains hidden. Clearly I'm not following the intended implementation strategy here. Could anyone point me in the right direction?
In winforms there were 2 templates: UserControl allows to make composite control (group of existing controls) to reuse it and standardize operations with it, CustomControl was a fully custom control (often rendered manually, performing differently than standard controls, etc), allowing overriding and provides access to protected things.
In wpf I only found UserControl template (using express edition of VS).
To create custom MyControl I am doing following:
create normal class, based on Control/ContentControl;
create resource dictionary with Style containing control template (could be only ControlTemplate, style is better, because can contain other properties setter)
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MySolution">
<Style x:Key="MyControl" TargetType="local:MyControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:MyControl">
<Grid x:Name="PART_Grid">
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
bind them together like this
public class MyControl : Control
{
public MyControl()
{
var dictionary = new ResourceDictionary();
dictionary.Source = new Uri("/MySolution;component/MyControl.xaml", UriKind.Relative);
Style = dictionary["MyControl"] as Style;
Loaded += MyControl_Loaded;
}
}
define lazy parts accessors in template
private Grid _partGrid;
private Grid PartGrid
{
get
{
if (_partGrid == null)
_partGrid = (Grid)Template.FindName("PART_Grid", this);
return _partGrid;
}
}
It works, but I am not sure if its the most optimal way:
.cs and .xaml are separate files (not one entity as in case of wpf UserControl);
when moving xaml (to example, inside Controls folder) constructor code has to be adjusted;
parts accessor are not available in constructor (template is yet not loaded).
My question: is there better way to make custom control with template? More comfortable, automatic, with automatic parts accessors, etc.
Here are my wpf templates
It is better if you use the CustomControl VS template, not sure why you don't see it, maybe because you are using the Express version. That template generates a Generic.xaml file for you, there you define the styles/controltemplate. Also you will have the .cs with the c# definition of your control.
To get the diferent parts of your control, the recommended way is using the GetTemplateChild mehtod and pass the Part_Name and you normally do that overriding the OnApplyTemplate() method.
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
ActionBtn = GetTemplateChild(BtnPartName) as ButtonBase;
ContentPopup = GetTemplateChild(PopupPartName) as Popup;
}
Take a look to this post to check a sample with the details of that implementation.