I've written a Markup extension for WPF that allows me to do
<!-- Generic Styles -->
<Style x:Key="bold" TargetType="Label">
<Setter Property="FontWeight" Value="ExtraBold" />
</Style>
<Style x:Key="italic" TargetType="Label">
<Setter Property="FontStyle" Value="Italic" />
</Style>
<Style x:Key="gridHeader" TargetType="Label"
BasedOn="{WPF:CombiStyle bold italic }" >
It is a very usefull extension and it works great at runtime. However
at design time I can't see the styles applied or that if
I mistype bold and italic they might not be found
as StaticResources.
Any hacks I can do to get this working?
The source code for the extension is
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Markup;
namespace MarkupExtensions
{
[MarkupExtensionReturnType(typeof(Style))]
public class CombiStyleExtension : MarkupExtension
{
private string[] MergeStyleProviders { get; set; }
public CombiStyleExtension(string s0)
{
MergeStyleProviders = s0.Split(new[]{' '});
}
public override object ProvideValue(IServiceProvider
serviceProvider)
{
return MergeStyleProviders
.Select(x => StringToStyle(serviceProvider, x))
.Aggregate(new Style(), RecursivelyMergeStyles);
}
private static Style StringToStyle(IServiceProvider serviceProvider, string x)
{
var style = new StaticResourceExtension() { ResourceKey = x }.ProvideValue(serviceProvider) as Style;
if (style==null)
{
throw new ArgumentException("Argument could not be converted to a style");
}
return style;
}
private static Style RecursivelyMergeStyles(Style accumulator,
Style next)
{
if (next.BasedOn != null)
{
RecursivelyMergeStyles(accumulator, next.BasedOn);
}
MergeStyle(accumulator, next);
return accumulator;
}
private static void MergeStyle(Style targetStyle, Style sourceStyle)
{
targetStyle.TargetType = sourceStyle.TargetType;
// Merge the Setters...
foreach (var setter in sourceStyle.Setters)
targetStyle.Setters.Add(setter);
// Merge the Triggers...
foreach (var trigger in sourceStyle.Triggers)
targetStyle.Triggers.Add(trigger);
}
}
}
Update: added screenshots for VS2012 (works fine) and Blend for VS2012 (works partially: base styles on BasedOn-styles are not picked up correctly for some reason).
Also checked it in VS2013 Preview and Blend for VS2013 Preview - there it works partially and exactly the same as in Blend for VS2012. Hope they fix this in release.
The thing is that Visual Studio designer very likes when object that you try to describe in XAML has public default constructor that it use to instanciate design-time instance of that object.
I've updated a bit your CombiStyleExtension.cs class to take this into account and Visual Studio 2010 designer like it now. However Blend 4 designer still don't, sorry.
Take a look:
using System;
using System.Linq;
using System.Windows;
using System.Windows.Markup;
namespace WpfApplication7
{
[MarkupExtensionReturnType(typeof(Style))]
public class CombiStyleExtension : MarkupExtension
{
/// <summary>
/// Set space-separated style names i.e. "size16 grey verdana".
/// </summary>
public string Names { private get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
return Names.Split(new[] { ' ' })
.Select(x => Application.Current.TryFindResource(x)
as Style)
.Aggregate(new Style(), RecursivelyMergeStyles);
}
private static Style RecursivelyMergeStyles(Style accumulator,
Style next)
{
if(accumulator == null || next == null)
return accumulator;
if(next.BasedOn != null)
RecursivelyMergeStyles(accumulator, next.BasedOn);
MergeStyle(accumulator, next);
return accumulator;
}
private static void MergeStyle(Style targetStyle, Style sourceStyle)
{
if(targetStyle == null || sourceStyle == null)
{
return;
}
targetStyle.TargetType = sourceStyle.TargetType;
// Merge the Setters...
foreach(var setter in sourceStyle.Setters)
targetStyle.Setters.Add(setter);
// Merge the Triggers...
foreach(var trigger in sourceStyle.Triggers)
targetStyle.Triggers.Add(trigger);
}
}
}
Usage of this markup extension changed also just a bit. How it was:
BasedOn="{WPF:CombiStyle bold italic }"
and how it now:
BasedOn="{WPF:CombiStyle Names='bold italic'}"
And just to save some time for you here is a bit of xaml to copy-paste-run-and-watch:
MainWindow.xaml:
<Window x:Class="WpfApplication7.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WPF="clr-namespace:WpfApplication7"
Title="MainWindow" Height="350" Width="569">
<Window.Resources>
<!-- Did not managed to make the type-level style work -->
<!-- from app.xaml, so put it here. Just in case. -->
<Style TargetType="{x:Type Label}"
BasedOn="{WPF:CombiStyle Names='size16 grey verdana'}" />
</Window.Resources>
<StackPanel>
<Label Content="Type-level: size16 + grey + verdana" />
<Label Content="'h1': size24 + royalBlue" Style="{DynamicResource h1}" />
<Label Content="'warning': size24 + yellow + bold + shadow"
Style="{DynamicResource warning}" />
<Label Content="Inline: size12 + italic"
Style="{WPF:CombiStyle Names='size12 italic'}" />
<Label Content="Inline: size16 + bold + italic + red"
Style="{WPF:CombiStyle Names='size16 bold italic red'}" />
<Label Content="Inline: size24 + green"
Style="{WPF:CombiStyle Names='size24 green'}" />
</StackPanel>
</Window>
App.xaml:
<Application x:Class="WpfApplication7.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WPF="clr-namespace:WpfApplication7"
StartupUri="MainWindow.xaml">
<Application.Resources>
<!-- Sizes -->
<Style x:Key="size12" TargetType="Label">
<Setter Property="FontSize" Value="12" />
</Style>
<Style x:Key="size16" TargetType="Label">
<Setter Property="FontSize" Value="16" />
</Style>
<Style x:Key="size24" TargetType="Label">
<Setter Property="FontSize" Value="24" />
</Style>
<!-- Bold/Italic -->
<Style x:Key="bold" TargetType="Label">
<Setter Property="FontWeight" Value="ExtraBold" />
</Style>
<Style x:Key="italic" TargetType="Label">
<Setter Property="FontStyle" Value="Italic" />
</Style>
<!-- Colors -->
<Style x:Key="grey" TargetType="Label">
<Setter Property="Foreground" Value="#333333" />
</Style>
<Style x:Key="royalBlue" TargetType="Label">
<Setter Property="Foreground" Value="RoyalBlue" />
</Style>
<Style x:Key="green" TargetType="Label">
<Setter Property="Foreground" Value="Green" />
</Style>
<Style x:Key="yellow" TargetType="Label">
<Setter Property="Foreground" Value="Yellow" />
</Style>
<Style x:Key="red" TargetType="Label">
<Setter Property="Foreground" Value="#D00000" />
</Style>
<!-- Fonts -->
<Style x:Key="verdana" TargetType="Label">
<Setter Property="FontFamily" Value="Verdana" />
</Style>
<!-- Effects -->
<Style x:Key="shadow" TargetType="Label">
<Setter Property="Effect">
<Setter.Value>
<DropShadowEffect ShadowDepth="0" />
</Setter.Value>
</Setter>
</Style>
<!-- Predefined Combinations -->
<Style x:Key="h1" TargetType="{x:Type Label}"
BasedOn="{WPF:CombiStyle Names='size24 royalBlue'}" />
<Style x:Key="warning" TargetType="{x:Type Label}"
BasedOn="{WPF:CombiStyle Names='size24 yellow bold shadow'}">
<Setter Property="BorderThickness" Value="1" />
<Setter Property="BorderBrush" Value="Yellow" />
</Style>
</Application.Resources>
</Application>
Enjoy ;)
No Unfortunately, My understanding of the problem is this,
Design time resolution seems to work in WPF because of dependency properties. Since MarkupExtension is not derived from dependency object your extension is never evaluated at design time. As to weather this was an oversight or intentional is debatable.
There may be another way to solve this though which would be slightly different. Create a new attached property called MergeStyles. In that property you could specify the style names you wish to merge and apply. When the value is changed simply update the style of the attachee using your code above. You could use the position each style to be merged comes in to determine hierarchy.
It's not exactly what you want but it may get you half way there. The upshot to this is that you can bind to it though.
Related
I have a an app that will open many windows, and I want all windows to look the same. I am overriding the default Windows window chrome style and making my own, so any new window that is opened (excluding messageboxes) should have the same window style. However, no matter what I seem to try it does not work. I can get it to work with one window, but when I want to make it a global style it always crashes or simply doesn't work as it should.
Here is my code:
WindowBaseStyle.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyProject.Styles"
xmlns:views="clr-namespace:Myproject.Views">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="GlobalStyles.xaml" />
</ResourceDictionary.MergedDictionaries>
<Style TargetType="{x:Type views:WindowBase}" BasedOn="{StaticResource {x:Type Window}}">
<Setter Property="AllowsTransparency" Value="False" />
<Setter Property="BorderBrush" Value="Red" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="WindowState" Value="Normal" />
<Setter Property="WindowStyle" Value="SingleBorderWindow" />
<Setter Property="WindowChrome.WindowChrome">
<Setter.Value>
<WindowChrome CaptionHeight="30"
UseAeroCaptionButtons="False"/>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type views:WindowBase}">
<Border BorderBrush="Blue" BorderThickness="1" SnapsToDevicePixels="True">
<DockPanel Background="White" LastChildFill="True" >
<Grid Background="Blue" DockPanel.Dock="Top">
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal" VerticalAlignment="Center">
<Button Name="PART_SystemMenuButton" Command="{Binding MenuCommand}" Style="{DynamicResource SystemIconButton}">
<Image Height="16" Width="16" Source="/Resources/icon.png" Stretch="Fill"/>
</Button>
<Viewbox Height="16" HorizontalAlignment="Stretch" Margin="14,2,0,0" >
<TextBlock FontSize="12" Foreground="White" Text="{Binding Title,RelativeSource={RelativeSource FindAncestor,AncestorType=Window}}" />
</Viewbox>
</StackPanel>
<StackPanel Orientation="Horizontal" WindowChrome.IsHitTestVisibleInChrome="True">
<Button x:Name="PART_MinimizeButton" Command="{Binding MinimizeCommand}" Height="30" Margin="0,0,0,0" ToolTip="Minimize" Width="45">
<Image Source="/Resources/minimize.png" Stretch="None" />
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="#0079CB" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#64AEEC"/>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
<Button x:Name="PART_MaximizeButton" Command="{Binding MaximizeCommand}" Height="30" Margin="0,0,0,0" Width="45">
<Image>
<Image.Style>
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, Path=WindowState}" Value="Normal">
<Setter Property="Source" Value="/Resources/maximize.png" />
<Setter Property="Stretch" Value="None" />
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, Path=WindowState}" Value="Maximized">
<Setter Property="Source" Value="/Resources/unmaximize.png" />
<Setter Property="Stretch" Value="None" />
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="#0079CB" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#64AEEC"/>
</Trigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, Path=WindowState}" Value="Normal">
<Setter Property="ToolTip" Value="Maximize" />
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, Path=WindowState}" Value="Maximized">
<Setter Property="ToolTip" Value="Restore Down" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
<Button x:Name="PART_CloseButton" Command="{Binding CloseCommand}" Height="30" Margin="0,0,0,0" ToolTip="Close" Width="45" >
<Image Source="/Resources/close.png" Stretch="None" />
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="#0079CB" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</StackPanel>
</StackPanel>
</Grid>
<!-- this ContentPresenter automatically binds to the content of the window -->
<ContentPresenter />
</DockPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
WindowBase.cs
using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interop;
namespace MyProject.Views
{
[TemplatePart(Name = "PART_MinimizeButton", Type = typeof(Button))]
[TemplatePart(Name = "PART_MaximizeButton", Type = typeof(Button))]
[TemplatePart(Name = "PART_CloseButton", Type = typeof(Button))]
[TemplatePart(Name = "PART_SystemMenuButton", Type = typeof(Button))]
public class WindowBase: Window
{
static WindowBase()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomWindow), new FrameworkPropertyMetadata(typeof(CustomWindow)));
}
public WindowBase()
{
Loaded += (sender, evnt) =>
{
var MinimizeButton = (Button)Template.FindName("PART_MinimizeButton", this);
var MaximizeButton = (Button)Template.FindName("PART_MaximizeButton", this);
var CloseButton = (Button)Template.FindName("PART_CloseButton", this);
var SystemMenuButton = (Button)Template.FindName("PART_SystemMenuButton", this);
MinimizeButton.Click += (s, e) => WindowState = WindowState.Minimized;
MaximizeButton.Click += (s, e) => WindowState = WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized;
CloseButton.Click += (s, e) => Close();
SystemMenuButton.Click += (s, e) => SystemCommands.ShowSystemMenu(this, GetMousePosition());
};
}
}
}
Window1.xaml
<local:WindowBase x:Class="MyProject.Views.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyProject.Views"
Height="750"
Width="1125">
<Grid>
</Grid>
</local:WindowBase>
Window1.xaml.cs
using System.Windows;
namespace MyProject.Views
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1: WindowBase
{
public Window1()
{
InitializeComponent();
}
}
}
I am overall following the MVVM pattern, and for the most part from all the articles and videos I have looked at online, they all follow this basic approach and they say it all works, but I can't seem to get it to work.
An additional note is that whenever I add my custom window control to the Window1.xaml file, it breaks the designer and says it is "Invalid Markup"
Also note I added my "WindowBaseStyle" resource dictionary to the App.xaml file as a merged resource dictionary.
Any help is greatly appreciated!! Thanks
Ok, as we discussed in the comments, it seems like the fastest solution to the problem you described is to use a StaticResource to get the window's style from a resource dictionary (or create an implicit style for windows.) I questioned the role of CustomWindow because I thought that might be causing problems with your default style override. (Remember: if you go the lookless control route and try to use the DefaultStyleKeyProperty override, you have to do this on every subclass of that control.)
However, I think doing something like this will get you reusable plumbing for multiple windows driven by viewmodels...
PopupHost
A class that would derive from your customized window. This code provides the following behaviors:
Allows the viewmodel to mark itself as having served its purpose, causing the window to close.
Allows attached properties to be specified by individual views that can affect the way the window appears on-screen, e.g. the window title.
Can be extended to notify presented items that the user has tried closing the window, allowing interception/cancellation or cleanup actions to be performed.
Code:
public class PopupHost : Window
{
private readonly AwaitableViewModelBase _viewModel;
public PopupHost(Window owner, AwaitableViewModelBase viewModel, string dataTemplateKey = null)
{
Owner = owner;
_viewModel = viewModel;
// Wrap the content in another presenter -- makes it a little easier to get to in order to look for attached properties.
var contentPresenter = new ContentPresenter
{
Content = viewModel
};
if (!string.IsNullOrWhiteSpace(dataTemplateKey))
contentPresenter.ContentTemplate = (DataTemplate) FindResource(dataTemplateKey);
Content = contentPresenter;
Task.Run(async () =>
{
await viewModel.Task;
Dispatcher.Invoke(Close);
});
Closed += ClosedHandler;
ApplyTemplate();
// Grab attached property values from the user control (or whatever element... you just need to find the descendant)
var contentElement = FindDescendantWithNonDefaultPropertyValue(contentPresenter, PopupWindowProperties.TitleProperty);
if (contentElement != null)
{
var binding = new Binding { Source = contentElement, Path = new PropertyPath(PopupWindowProperties.TitleProperty) };
SetBinding(TitleProperty, binding);
}
}
private void ClosedHandler(object sender, EventArgs args)
{
_viewModel?.Cancel();
Closed -= ClosedHandler;
}
private static Visual FindDescendant(Visual element, Predicate<Visual> predicate)
{
if (element == null)
return null;
if (predicate(element))
return element;
Visual foundElement = null;
(element as FrameworkElement)?.ApplyTemplate();
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++)
{
var visual = VisualTreeHelper.GetChild(element, i) as Visual;
foundElement = FindDescendant(visual, predicate);
if (foundElement != null)
break;
}
return foundElement;
}
private static Visual FindDescendantWithNonDefaultPropertyValue(Visual element, DependencyProperty dp)
{
return FindDescendant(element, e => !(dp.GetMetadata(e).DefaultValue ?? new object()).Equals(e.GetValue(dp)));
}
}
PopupWindowProperties
Just a dumb object containing solely attached properties so your views can convey some information to the window.
public static class PopupWindowProperties
{
public static readonly DependencyProperty TitleProperty = DependencyProperty.RegisterAttached("Title", typeof(string), typeof(PopupWindowProperties), new FrameworkPropertyMetadata(string.Empty));
public static void SetTitle(UIElement element, string value) => element.SetValue(TitleProperty, value);
public static string GetTitle(UIElement element) => element.GetValue(TitleProperty) as string;
}
AwaitableViewModelBase
A simple abstract viewmodel which has a TaskCompletionSource. This allows the popup window and the viewmodel to coordinate closing.
public abstract class AwaitableViewModelBase : ViewModelBase
{
protected TaskCompletionSource<bool> TaskCompletionSource { get; set; }
public Task<bool> Task => TaskCompletionSource?.Task;
public void RegisterTaskCompletionSource(TaskCompletionSource<bool> tcs)
{
var current = TaskCompletionSource;
if (current != null && current.Task.Status == TaskStatus.Running)
throw new InvalidOperationException();
TaskCompletionSource = tcs;
}
public virtual void Cancel() => SetResult(false);
protected void SetResult(bool result) => TaskCompletionSource?.TrySetResult(result);
}
WindowService
Last but not least, the simple service that can present the requested view and viewmodel. You can use implicit DataTemplates for your viewmodels, or provide the specific x:Key value of the template you wish to use. Note that the await doesn't really do anything here because ShowDialog blocks. We return the bool since it can be used to easily identify if the user hit OK or Cancel on a modal.
public class WindowService
{
public async Task<bool> ShowModalAsync(AwaitableViewModelBase viewModel, string dataTemplateKey = null)
{
var tcs = new TaskCompletionSource<bool>();
viewModel.RegisterTaskCompletionSource(tcs);
Application.Current.Dispatcher.Invoke(() =>
{
var currentWindow = Application.Current.Windows.OfType<Window>().SingleOrDefault(x => x.IsActive) ?? Application.Current.MainWindow;
var window = new PopupHost(currentWindow, viewModel, dataTemplateKey);
window.ShowDialog();
});
return await viewModel.Task;
}
}
The goal is to check the state of parameters, the state of each parameter can take the enum value (lock, unlock, or valueIncorrect). The display
will be different according to the state of the parameter(e.g, lock rectangle will be red, unlock the text will be bold and vallueIncorrect will be gray).
In main class we have different parameters VarA, VarB and VarC for example. The state of each parameter can be different.
enum State{lock, unlock, valueIncorrect};
State VarA = lock
State VarB = unlock
State VarC = lock
before I was this style and a rectangle based on this style and When I pressed a button , the style will change, but the reaction will be the same on all parameter
<Style x:Key="DisplayLockGroup" TargetType="GroupBox">
<Setter Property="BorderThickness" Value="2"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Source={x:Static local:LockMgt.Instance}, Path=isLocked}" Value="true">
<Setter Property="BorderBrush" Value="Red" />
</DataTrigger>
<DataTrigger Binding="{Binding Source={x:Static local:LockMgt.Instance}, Path=isLocked}" Value="false">
<Setter Property="BorderBrush" Value="Green" />
</DataTrigger>
</Style.Triggers>
</Style>
<Rectangle x:Name="r_LockEcuTypes" Grid.Row="5" Grid.RowSpan="3" Grid.Column ="1" Grid.ColumnSpan="7" Style="{DynamicResource DisplayLockRectangleGroup}" />
Now i would like to change this behavior, I have different parameter and I would like to individually check the state of this parameter and apply one style according to the value, but i don't know How I can do it.
E.g for one parameter:
State VarA=lock => i would like to apply the style1
State VarB=unlock => i would like to apply the style2
State VarC=lock => i would like to apply the style1 but if the value change from lock to unlock, I would like to apply the style2
I don't know how create the XAML to display correctly what is expected.
As commented, depending on your actual needs, you have several options.
Converter from State to some property value
Change the properties via Style.Triggers (similar to your current approach)
Wrap your control and exchange inner styles via ControlTemplate.Triggers on the wrapper control
The following example is presenting three rectangles (rect1, rect2 and rect3 representing the approach with the same number) and a button that is responsible for changing the State of the ViewModel object in order to show how each rectangle reacts.
The XAML
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication2"
Title="MainWindow" Height="350" Width="525"
Loaded="Window_Loaded">
<Window.Resources>
<!-- Used for 1. -->
<local:StateToBorderBrushConverter x:Key="stateConverter"/>
<!-- Used for 2. -->
<Style x:Key="DisplayLockGroup" TargetType="Rectangle">
<Setter Property="StrokeThickness" Value="2"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Data1State}" Value="{x:Static local:State.locked}">
<Setter Property="Stroke" Value="Red" />
</DataTrigger>
<DataTrigger Binding="{Binding Data1State}" Value="{x:Static local:State.unlock}">
<Setter Property="Stroke" Value="Green" />
</DataTrigger>
</Style.Triggers>
</Style>
<!-- Used for 3. -->
<Style x:Key="DefaultRectangleStyle" TargetType="Rectangle">
<Setter Property="StrokeThickness" Value="2"/>
</Style>
<Style x:Key="LockedRectangleStyle" TargetType="Rectangle">
<Setter Property="StrokeThickness" Value="2"/>
<Setter Property="Stroke" Value="Red" />
</Style>
<Style x:Key="UnlockedRectangleStyle" TargetType="Rectangle">
<Setter Property="StrokeThickness" Value="2"/>
<Setter Property="Stroke" Value="Green" />
</Style>
</Window.Resources>
<Grid x:Name="grid1">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Rectangle x:Name="rect1" Grid.Row="0" Margin="5" StrokeThickness="2" Stroke="{Binding Data1State,Converter={StaticResource stateConverter}}" />
<Rectangle x:Name="rect2" Grid.Row="1" Margin="5" Style="{StaticResource DisplayLockGroup}" />
<ContentControl Margin="5" Grid.Row="2">
<ContentControl.Template>
<ControlTemplate TargetType="ContentControl">
<Rectangle x:Name="rect3" Style="{StaticResource DefaultRectangleStyle}" />
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding Data1State}" Value="{x:Static local:State.locked}">
<Setter TargetName="rect3" Property="Style" Value="{StaticResource LockedRectangleStyle}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Data1State}" Value="{x:Static local:State.unlock}">
<Setter TargetName="rect3" Property="Style" Value="{StaticResource UnlockedRectangleStyle}"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</ContentControl.Template>
</ContentControl>
<Button x:Name="ChangeStateButton" Grid.Row="3" Margin="5" VerticalAlignment="Center" Content="Change State" Click="ChangeStateButton_Click"/>
</Grid>
</Window>
The Code
namespace WpfApplication2
{
public enum State
{
locked,
unlock,
valueIncorrect
}
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged([CallerMemberName] string prop = null)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(prop));
}
}
}
public class MyData : BaseViewModel
{
private State _Data1State;
public State Data1State
{
get { return _Data1State; }
set
{
if (_Data1State != value)
{
_Data1State = value;
NotifyPropertyChanged();
}
}
}
}
public class StateToBorderBrushConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is State)
{
var s = (State)value;
switch (s)
{
case State.locked:
return new SolidColorBrush(Colors.Red);
case State.unlock:
return new SolidColorBrush(Colors.Green);
case State.valueIncorrect:
return new SolidColorBrush(Colors.White);
default:
break;
}
}
return new SolidColorBrush(Colors.Transparent);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private MyData ContextData;
private void Window_Loaded(object sender, RoutedEventArgs e)
{
ContextData = new MyData { Data1State = State.locked };
grid1.DataContext = ContextData;
}
private void ChangeStateButton_Click(object sender, RoutedEventArgs e)
{
switch (ContextData.Data1State)
{
case State.locked:
ContextData.Data1State = State.unlock;
break;
case State.unlock:
ContextData.Data1State = State.valueIncorrect;
break;
case State.valueIncorrect:
ContextData.Data1State = State.locked;
break;
default:
ContextData.Data1State = State.locked;
break;
}
}
}
}
Pros and cons:
The converter approach is useful, if the same property type is targeted in many different control types. The converter is returning a Brush and it doesn't care whether that brush will be used in a Rectangle.Stroke, a Border.BorderBrush or some other context.
The style trigger approach is less flexible regarding the targeted control type, but it is easier to maintain changes of more than a single property based on the status.
The control template trigger approach is useful in some advanced scenarios. It allows separate definition of the styles for each state. However, I'd only recommend it if you actually derive your own custom control with additional functionality, not as an ad-hoc control template just for switching styles.
You can apply an enum to color converter in the XAML binding
Background="{Binding Source=StateValue,
Converter={StaticResource stateValueColorConverter}}"
You'll easily find the detailed documentation: this is to give you the main idea of the usage.
Thanks to grek40, it is exactly what I want. I have try the option 3 and 2 and it works fine, I have use the case 2, it is most easily to implement.
I have use the same behavior for add an image on lock and unlock state.
In the Xaml.cs file, How I can access to parameter rect3 on your proposal, when I try it I have compilation error "the name "rect3" does not exist in the current view, I have written
XAML.CS :
rect3.Visibility = Visibility.Collapsed;
XAML :
<ContentControl Grid.RowSpan="3" Grid.ColumnSpan="3" KeyboardNavigation.IsTabStop="False">
<ContentControl.Template>
<ControlTemplate TargetType="ContentControl">
<Rectangle x:Name="rect3" Style="{StaticResource DefaultRectangleStyle}" />
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding Data1State}" Value="{x:Static local:State.locked}">
<Setter TargetName="rect3" Property="Style" Value="{StaticResource LockedRectangleStyle}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Data1State}" Value="{x:Static local:State.unlock}">
<Setter TargetName="rect3" Property="Style" Value="{StaticResource UnlockedRectangleStyle}"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</ContentControl.Template>
</ContentControl>
I'm using this extension this toolkit, it automatically adds a TextBox to the top of each DataGrid column for filtering from the following code.
<DataGrid ColumnHeaderStyle="{StaticResource {ComponentResourceKey
TypeInTargetAssembly={x:Type filter:DataGridHeaderFilterControl},
ResourceId=DataGridHeaderFilterControlStyle}}" >
However, the added column header and the textboxes have a different style from what I want.
So I can do this to style the column header, but it won't change the textboxes.
<Style x:Key="FilterHeader"
BasedOn="{StaticResource {ComponentResourceKey TypeInTargetAssembly={x:Type filter:DataGridHeaderFilterControl},
ResourceId=DataGridHeaderFilterControlStyle}}"
TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="Foreground" Value="Blue" />
</Style>
...
<DataGrid ColumnHeaderStyle="{DynamicResource FilterHeader}">
I tried putting this inWindow.Resources to see if it had an effect on the textboxes. It changed my other textboxes it doesn't have any effect on the textboxes created by the extension.
<Style TargetType="TextBox">
<Setter Property="Foreground" Value="Blue" />
</Style>
But no dice. The only thing I've found that works is this:
public void DataGrid_OnLoad(object sender, RoutedEventArgs e)
{
IEnumerable<System.Windows.Controls.TextBox> collection =
FindVisualChildren<System.Windows.Controls.TextBox>(TitleBar);
foreach (System.Windows.Controls.TextBox tb in collection)
{
tb.Foreground = Brushes.Blue;
}
}
public static IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject
{
if (depObj != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
if (child != null && child is T)
{
yield return (T)child;
}
foreach (T childOfChild in FindVisualChildren<T>(child))
{
yield return childOfChild;
}
}
}
}
But that seems like a garbage way of doing it. How can I do this purely in xaml or at least more cleanly in C#?
From looking through the source code, i noticed that the TextBox isn't really a textbox but a subclass of Textbox. DelayTextBox
So you could just modify your style to target that instead.
<Style TargetType="DelayTextBox">
<Setter Property="Foreground" Value="Blue" />
</Style>
I haven't tried it but it should work.
Update
Finally found the issue. The styles should be defined within a style of their parent.
<Style TargetType="{x:Type filter:DataGridColumnFilter}">
<Style.Resources>
<Style TargetType="{x:Type support:DelayTextBox}">
<Setter Property="Foreground" Value="Blue" />
<Setter Property="FontWeight" Value="Bold"/>
</Style>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="Foreground" Value="Blue" />
<Setter Property="FontWeight" Value="Bold"/>
</Style>
</Style.Resources>
</Style>
I've added the comboxbox since i assume you'd want that too. you can take out the setter for bold as needed .. was using it to test.
Let's say, I've got something like this (in MainPage.xaml):
<Page.Resources>
<Style TargetType="TextBlock" x:Key="TextBlockStyle">
<Setter Property="FontFamily" Value="Segoe UI Light" />
<Setter Property="Background" Value="Navy" />
</Style>
</Page.Resources>
Then, I would like to apply that StaticResource style to my dynamic created TextBlock (file MainPage.xaml.cs).
Is there any possibility to do this instead of doing something like this:
myTextBlock.FontFamily = new FontFamily("Segoe UI Light");
myTextBlock.Background = new SolidColorBrush(Color.FromArgb(255,0,0,128));
It has been more than 4 years now since this question was asked, but I want to post an answer just to share my findings.
For example if there is a Style BlueButton described in Application resource in App.xaml (Xamarin Cross-Platform App development), it can be used as follows
<?xml version="1.0" encoding="utf-8" ?><Application xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="SharedUi.App">
<Application.Resources>
<ResourceDictionary>
<Style x:Key="BlueButton" TargetType="Button">
<Setter Property="TextColor" Value="White" />
<Setter Property="FontSize" Value="20" />
<Setter Property="BackgroundColor" Value="Blue"/>
<Setter Property="HeightRequest" Value="70"/>
<Setter Property="FontAttributes" Value="Bold"/>
</Style>
</ResourceDictionary>
</Application.Resources></Application>
Then in the code behind
Button newButton1 = new Button
{
Text = "Hello",
WidthRequest = (double)15.0,
Style = (Style)Application.Current.Resources["BlueButton"]
};
You can set, Something like this,
TextBlock myTextBlock= new TextBlock ()
{
FontFamily = new FontFamily("Segoe UI Light");
Style = Resources["TextBlockStyle"] as Style,
};
You can use this:
Style textBlockStyle;
try
{
textBlockStyle = FindResource("TextBlockStyle") as Style;
}
catch(Exception ex)
{
// exception handling
}
if(textBlockStyle != null)
{
myTextBlock.Style = textBlockStyle;
}
or TryFindResource approach:
myTextBlock.Style = (Style)TryFindResource("TextBlockStyle");
I want to attain a list of styles that apply to a specific control type. I'd like to do something like the code below, but not have to specify a key name and get a list of applicable resources back. Is this possible?
ComponentResourceKey key = new ComponentResourceKey(typeof(MyControlType), "");
Style style = (Style)Application.Current.TryFindResource(key);
Would it make sense to first examine the Resources of your control, and then continue walking up the VisualTree examining Resources along the way to simulate how WPF finds resources for your control (including Styles)?
For example, maybe you could create an extension method that does this given a FrameworkElement:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Media;
namespace WpfApplication2
{
public static class FrameworkElementHelper
{
public static IEnumerable<Style> FindStylesOfType<TStyle>(this FrameworkElement frameworkElement)
{
IEnumerable<Style> styles = new List<Style>();
var node = frameworkElement;
while (node != null)
{
styles = styles.Concat(node.Resources.Values.OfType<Style>().Where(i => i.TargetType == typeof(TStyle)));
node = VisualTreeHelper.GetParent(node) as FrameworkElement;
}
return styles;
}
}
}
To see that this works, create XAML file that has both implicit and explicit Styles at multiple levels in the VisualTree:
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:WpfApplication2"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="MainWindow" SizeToContent="WidthAndHeight">
<Window.Resources>
<Style TargetType="{x:Type Button}" />
<Style x:Key="NamedButtonStyle" TargetType="{x:Type Button}" />
<Style TargetType="{x:Type TextBlock}" />
<Style x:Key="NamedTextBlockStyle" TargetType="{x:Type TextBlock}" />
</Window.Resources>
<StackPanel>
<TextBlock x:Name="myTextBlock" Text="No results yet." />
<Button x:Name="myButton" Content="Find Styles" Click="OnMyButtonClick">
<Button.Resources>
<Style TargetType="{x:Type Button}" />
<Style x:Key="NamedButtonStyle" TargetType="{x:Type Button}" />
<Style TargetType="{x:Type TextBlock}" />
<Style x:Key="NamedTextBlockStyle" TargetType="{x:Type TextBlock}" />
</Button.Resources>
</Button>
</StackPanel>
</Window>
and with the following handler in the code behind:
private void OnMyButtonClick(object sender, RoutedEventArgs e)
{
var styles = myButton.FindStylesOfType<Button>();
myTextBlock.Text = String.Format("Found {0} styles", styles.Count());
}
In this example, 4 styles are found for the myButton all of which have a TargetType of Button. I hope this can be a good starting point. Cheers!