Template binding inside a ResourceDictionary Control Template - wpf

I have a resource dictionary as follows,
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:uwpControls="using:Microsoft.Toolkit.Uwp.UI.Controls"
xmlns:controls="using:Presentation.Common.Controls">
<Style x:Key="ExpanderStyleSection" TargetType="uwpControls:Expander">
<Setter Property="HeaderStyle" Value="{StaticResource LightExpanderHeaderToggleButtonStyle}"/>
<Setter Property="Margin" Value="4"/>
</Style>
<Style x:Key="LightExpanderHeaderToggleButtonStyle" TargetType="ToggleButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Grid x:Name="RootGrid"
Background="{StaticResource BrushBeckmanAquaA1}"> <!-- I want to change the background from an other View based on some condition -->
</ControlTemplate>
</Style>
</ResourceDictionary>
Now I am using the style inside this Dictionary as follows in an other view.
<uwpControls:Expander x:Name="ExpanderLisSharedSettings" Grid.Row="0" Style="{StaticResource ExpanderStyleSection}">
<!--Some Content here-->
</uwpControls:Expander
Whenever a property in the ViewModel changes, I want to update the RootGrid background in the ControlTemplate to some other color. How can I do it? In other words is there a way to create a dependency property of type Brush color and bind to the RootGrid Background in the ResourceDictionary. Please help.

You can create a ViewModel which contains a SolidColorBrush property and bind it with the Background of RootGrid and you need to declare the DataContext in the page which you use this style. In that case, it applies for Binding.
If you want to use x:bind in ResourceDictionary, you need to create a code behind class for it. Since the x:Bind depends on code generation, so it needs a code-behind file containing a constructor that calls InitializeComponent (to initialize the generated code).
Here we take binding as an example.
Page.xaml:
<ToggleButton Style="{StaticResource LightExpanderHeaderToggleButtonStyle}"></ToggleButton>
Page.xaml.cs:
public BlankPage1()
{
this.InitializeComponent();
VM = new MyViewModel();
VM.Fname = "fleegu";
VM.MyColor = new SolidColorBrush(Colors.Green);
this.DataContext = VM;
}
public MyViewModel VM { get; set; }
ResourceDictionary.xaml:
<Style x:Key="LightExpanderHeaderToggleButtonStyle" TargetType="ToggleButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Grid x:Name="RootGrid" Background="{Binding MyColor}">
<TextBlock Text="{Binding Fname}"></TextBlock>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
MyViewModel:
public class MyViewModel: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private string _fname { get; set; }
private SolidColorBrush myColor { get; set; }
public string Fname
{
get { return _fname; }
set
{
_fname = value;
this.OnPropertyChanged();
}
}
public SolidColorBrush MyColor
{
get { return myColor; }
set
{
myColor = value;
this.OnPropertyChanged();
}
}
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}

Use a TemplateBinding in the template. You may use a <Setter> to define the default value:
<Style x:Key="LightExpanderHeaderToggleButtonStyle" TargetType="ToggleButton">
<Setter Property="Background" Value="{StaticResource BrushBeckmanAquaA1}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Grid x:Name="RootGrid" Background="{TemplateBinding Background}">
...
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
You can then set or bind the Background of each instance of the control:
<uwpControls:Expander x:Name="ExpanderLisSharedSettings" Grid.Row="0" Background="Red" />
For example, if the view model defines a Brush property called "TheBackground", you could bind to it as usual (only UWP supports {x:Bind}):
<uwpControls:Expander x:Name="ExpanderLisSharedSettings" Grid.Row="0"
Background="{Binding TheBackground}" />

Related

How to dynamically set style of an element in a User Control

In a WPF project, I have a user control (Valve.xaml) that defines a Polygon shape.
<Grid>
<Polygon Name="pValve" Points="0,50 0,20 50,50 50,20" Style="{StaticResource Valve_Open}"/>
</Grid>
I am displaying the Valve user control in a window xaml (FFG.xaml) file, like such:
<Window
<!-- removed other namespaces for brevity -->
xmlns:cl="clr-namespace:FFG.Controls;assembly=PID.Controls">
<Grid>
<cl:Valve x:Name="valve201A"></cl:Valve>
</Grid>
</Window>
I am setting the DataContext of FFG.xaml to class FFG_ViewModel.cs, and it contains an instance of the Valve_Model class. Valve_Model essentially represents the valve that is drawn on the window in FFG.xaml.
public class FFG_ViewModel : ViewModelBase {
public Valve_Model Valve201A { get; set; }
// There are other properties and methods, but I've removed them for brevity also
}
Here is the Valve_Model class:
public class Valve_Model INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
private bool _isValveOpen { get; set; }
public bool IsValveOpen {
get {
return _isValveOpen;
}
set {
_isValveOpen = value;
OnPropertyChanged("IsValveOpen");
}
}
#region INotifyPropertyChanged
protected virtual void OnPropertyChanged(string propertyName) {
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null) {
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
#endregion
}
QUESTION:
What I want is for the Style property in the Valve.xaml to change when the IsValveOpen property changes.
So if the valve is open then it would be:
<Polygon Name="pValve" Points="0,50 0,20 50,50 50,20" Style="{StaticResource Valve_Open}"/>
and when the property is changed to false then I need the style of the polygon to be changed to:
<Polygon Name="pValve" Points="0,50 0,20 50,50 50,20" Style="{StaticResource Valve_Closed}"/>
How do I go about implementing this exactly?
You could use an IMultiValueConverter.
First, let's simplify the use case. Basicly you want to swap Styles based on a given state object, which I'll represent by a ToggleButton. The fact that you're wrapping everything in a UserControl also has no influence on the underlying concept.
Demo:
Starting a fresh project
Declaring our Resources
Feeding the Window and the state to the Converter.
MainWindow.xaml
<Window
...
>
<Window.Resources>
<local:ToStyleConverter x:Key="ToStyleConverter"/>
<Style x:Key="Valve_Open" TargetType="{x:Type Polygon}">
<Setter Property="Fill" Value="Red"/>
</Style>
<Style x:Key="Valve_Closed" TargetType="{x:Type Polygon}">
<Setter Property="Fill" Value="Green"/>
</Style>
</Window.Resources>
<DockPanel>
<ToggleButton x:Name="butt" DockPanel.Dock="Bottom">Switch</ToggleButton>
<Polygon Name="pValve" Points="0,50 0,20 50,50 50,20" Stretch="Uniform">
<Polygon.Style>
<MultiBinding Converter="{StaticResource ToStyleConverter}">
<Binding RelativeSource="{RelativeSource FindAncestor,
AncestorType={x:Type Window}}"/>
<Binding ElementName="butt" Path="IsChecked"/>
</MultiBinding>
</Polygon.Style>
</Polygon>
</DockPanel>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
public class ToStyleConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
if (values[0] is Window)
{
Window win = (Window)values[0];
if ((bool)values[1])
return win.FindResource("Valve_Open");
if (!(bool)values[1])
return win.FindResource("Valve_Closed");
}
return DependencyProperty.UnsetValue;
}
public object[] ConvertBack(object values, Type[] targetType,
object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Changing to any specific use case means:
Pointing the relativesource binding to the Control that holds the Resources (Styles)
Using the second binding to add the state to the Converter (DP/INPC)
Implementing Converter logic
You can (should as much as I know) use a DataTrigger, within a ControlTemplate. Assuming that these two are your Styles:
<Window.Resources>
<Style TargetType="Polygon" x:Key="Valve_Open">
<Setter Property="Fill" Value="Red"/>
</Style>
<Style TargetType="Polygon" x:Key="Valve_Close">
<Setter Property="Fill" Value="Green"/>
</Style>
</Window.Resources>
You should add this style to the resources:
<Style x:Key="changeStyle" TargetType="Control">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Control">
<Grid>
<Polygon Name="pValve" Points="0,50 0,20 50,50 50,20" Style="{StaticResource Valve_Open}" />
</Grid>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding Valve201A.IsValveOpen}" Value="true">
<Setter TargetName="pValve" Property="Style" Value="{StaticResource Valve_Close}" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
and use them in your views:
<Control DataContext="{Binding}" Style="{StaticResource changeStyle}" />
Instead of setting the actual Style property to a new value, you could add a DataTrigger to the Style itself that changes the properties of the Polygon based on the value of the IsValveOpen source property.
Valve.xaml:
<Grid>
<Polygon Name="pValve" Points="0,50 0,20 50,50 50,20">
<Polygon.Style>
<Style TargetType="Polygon">
<!-- Copy the setters from the Valve_Closed style here -->
<Setter Property="Fill" Value="Red" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsValveOpen}" Value="True">
<!-- Copy the setters from the Valve_Open style here -->
<Setter Property="Fill" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</Polygon.Style>
</Polygon>
</Grid>
FFG.xaml:
<Grid>
<cl:Valve x:Name="valve201A" DataContext="{Binding Valve201A}" />
</Grid>

Automatically inherit control styles in only one custom window

I am wondering if it is possible to associate Styles for certain controls with a custom window in WPF.
Here's the scenario - I have created a custom window, and have defined styles for a number of controls that I will use in this window. These are contained in a portable class library.
The catch is that I only want the controls to use the style from my library when they are used in the custom window (there are several different windows in the application).
I understand that I can assign the styles a key, and load them from my portable library in my application's app.xaml using pack syntax, for example:
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Custom.Application.Library.Controls;component/Styles/CheckBox.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
And then add and style the control within my custom window as such:
<CheckBox x:Name="checkBox" Style="{StaticResource SpecialCheckBox}"
But what I would really like to do is define they styles in my class library without a key, as in this:
<Style TargetType="{x:Type CheckBox}">
Instead of this:
<Style x:Key="SpecialCheckBox" TargetType="{x:Type CheckBox}">
So that when this checkbox is used in my custom window it automatically inherits the style. If I define the style like this, and load it into my app.xaml, the problem is obviously that ALL checkboxes will inherit this style, not just checkboxes used in my custom window.
So, what I'm trying to find out is if there is any way to associate a style resource explicitly with a custom window, so that I can define the styles without a key, and have them by default inherit the "Special" style when used in my custom window, but use the WPF defaults in any other windows of the application. Does anyone have experience with this?
For clarity here is the code of my custom window:
XAML:
<!-- Window style -->
<Style TargetType="{x:Type Controls:CCTApplicationWindow}">
<Setter Property="WindowStyle" Value="None"/>
<Setter Property="AllowsTransparency" Value="True"/>
<Setter Property="ResizeMode" Value="CanResizeWithGrip"/>
<Setter Property="MinWidth" Value="500"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Controls:CCTApplicationWindow}">
<Border BorderBrush="#FF999999">
<Border.Style>
<Style TargetType="{x:Type Border}">
<Setter Property="BorderThickness" Value="1"/>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=WindowState}" Value="Maximized">
<Setter Property="BorderThickness" Value="7"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<Grid>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="29"/>
<RowDefinition />
</Grid.RowDefinitions>
<Controls:CCTApplicationHeader Grid.Row="0"
Margin="0"
Title="{TemplateBinding Title}"
DragMoveCommand="{TemplateBinding DragMoveCommand}"
MaximizeCommand="{TemplateBinding MaximizeCommand}"
MinimizeCommand="{TemplateBinding MinimizeCommand}"
CloseCommand="{TemplateBinding CloseCommand}"/>
<Grid Background="White" Grid.Row="1" Margin="0">
<AdornerDecorator>
<ContentPresenter/>
</AdornerDecorator>
</Grid>
</Grid>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
CS:
public partial class CCTApplicationWindow : Window
{
public static readonly DependencyProperty MaximizeCommandProperty = DependencyProperty.Register("MaximizeCommand", typeof(DelegateCommand), typeof(CCTApplicationWindow));
public static readonly DependencyProperty MinimizeCommandProperty = DependencyProperty.Register("MinimizeCommand", typeof(DelegateCommand), typeof(CCTApplicationWindow));
public static readonly DependencyProperty CloseCommandProperty = DependencyProperty.Register("CloseCommand", typeof(DelegateCommand), typeof(CCTApplicationWindow));
public static readonly DependencyProperty DragMoveCommandProperty = DependencyProperty.Register("DragMoveCommand", typeof(DelegateCommand), typeof(CCTApplicationWindow));
public CCTApplicationWindow()
{
MaximizeCommand = new DelegateCommand(MaximizeExecute);
MinimizeCommand = new DelegateCommand(MinimizeExecute);
CloseCommand = new DelegateCommand(CloseExecute);
DragMoveCommand = new DelegateCommand(DragMoveExecute);
}
static CCTApplicationWindow()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CCTApplicationWindow), new FrameworkPropertyMetadata(typeof(CCTApplicationWindow)));
}
public DelegateCommand MaximizeCommand
{
get
{
return (DelegateCommand)GetValue(MaximizeCommandProperty);
}
set
{
SetValue(MaximizeCommandProperty, value);
}
}
public DelegateCommand MinimizeCommand
{
get
{
return (DelegateCommand)GetValue(MinimizeCommandProperty);
}
set
{
SetValue(MinimizeCommandProperty, value);
}
}
public DelegateCommand CloseCommand
{
get
{
return (DelegateCommand)GetValue(CloseCommandProperty);
}
set
{
SetValue(CloseCommandProperty, value);
}
}
public DelegateCommand DragMoveCommand
{
get
{
return (DelegateCommand)GetValue(DragMoveCommandProperty);
}
set
{
SetValue(DragMoveCommandProperty, value);
}
}
private void MaximizeExecute(object obj)
{
if (this.WindowState != WindowState.Maximized)
{
this.WindowState = WindowState.Maximized;
}
else
{
SystemCommands.RestoreWindow(this);
}
}
private void MinimizeExecute(object obj)
{
SystemCommands.MinimizeWindow(this);
}
private void CloseExecute(object obj)
{
SystemCommands.CloseWindow(this);
}
private void DragMoveExecute(object obj)
{
DragMove();
}
}
Yes, you can do this, but you shouldn't! You've tagged this question as MVVM and yet your architecture design breaks MVVM entirely. The whole point of MVVM is that view logic is contained within the view model layer; your view models are the ones that should be keeping track of the logical hierarchy and they are the ones that should be exposing properties to the views to control their appearance. To put it another way, just because XAML is flexible enough and powerful enough to implement such logic doesn't mean it's the best place to actually do it!
To answer your question though, yes, this can be done with a DataTrigger binding to the parent with ObjectToTypeConverter. Here's an example of setting the TextBlock background to CornflowerBlue, unless its immediate parent is a Grid in which case it should be set to PaleGoldenrod:
<StackPanel Orientation="Vertical">
<StackPanel.Resources>
<converters:ObjectToTypeConverter x:Key="ObjectToTypeConverter" />
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Background" Value="CornflowerBlue" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Parent, RelativeSource={RelativeSource Mode=Self}, Converter={StaticResource ObjectToTypeConverter}}" Value="{x:Type Grid}">
<Setter Property="Background" Value="PaleGoldenrod" />
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Resources>
<Grid Width="100" Height="32" HorizontalAlignment="Left">
<TextBlock Text="TextBox A" /> <!-- Gets a PaleGoldenrod background -->
</Grid>
<Canvas Width="100" Height="32" HorizontalAlignment="Left">
<TextBlock Text="TextBox B" /> <!-- Gets a CornflowerBlue background -->
</Canvas>
</StackPanel>
And here's the converter code. It's worth pointing out that if you're happy to simply check that a parent of a given type exists somewhere in the hierarchy (as opposed to the immediate parent) then you don't even need this, you can just attempt to bind to RelativeSource with AncestorType set to the relevant parent type.
// based on http://stackoverflow.com/questions/8244658/binding-to-the-object-gettype
[ValueConversion(typeof(object), typeof(Type))]
public class ObjectToTypeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value == null ? null : value.GetType();
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new InvalidOperationException();
}
}
But again, I implore you, if you really do want to adhere to MVVM then do not do it like this! This is exactly the kind of problem that "proper" MVVM was designed to solve.
Simplest way is to create a separate ResourceDictionary for your Custom window. And use it either using XAML or load it using Code.

Set GroupStyle inside style on xaml

I'm Trying to set default style for ContexMenu and I want to set default GroupStyle to ContexMenu inside the style. Something like this:
<Setter Property="ItemsControl.GroupStyle">
<Setter.Value>
<GroupStyle>
<...>
</GroupStyle>
</Setter.Value>
</Setter>
But the compiler say error: it can't find GroupStyle on ItemsControl.
However ,in code I can do simply:
ContextMenu contextMenu;
contextMenu.GroupStyle.Add(someSavedStyle);
How can I achieve this in xaml?
Thanks in advance!
You can use an attached property to simplify adding group styles:
<Style TargetType="MenuItem">
<Setter Property="b:GroupStyleEx.Append">
<Setter.Value>
<GroupStyle .. />
</Setter.Value>
</Setter>
<!-- you can add as many as you like... -->
<Setter Property="b:GroupStyleEx.Append">
<Setter.Value>
<!-- second group style -->
<GroupStyle .. />
</Setter.Value>
</Setter>
</Style>
Here's the code for the attached property:
using System;
using System.Windows;
using System.Windows.Controls;
namespace Util
{
public static class GroupStyleEx
{
public static readonly DependencyProperty AppendProperty
= DependencyProperty.RegisterAttached("Append", typeof (GroupStyle), typeof (GroupStyleEx),
new PropertyMetadata(AppendChanged));
public static GroupStyle GetAppend(DependencyObject obj)
{
return (GroupStyle) obj.GetValue(AppendProperty);
}
public static void SetAppend(DependencyObject obj, GroupStyle style)
{
obj.SetValue(AppendProperty, style);
}
private static void AppendChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var itemsControl = d as ItemsControl;
if (itemsControl == null)
throw new InvalidOperationException("Can only add GroupStyle to ItemsControl");
var #new = e.NewValue as GroupStyle;
if (#new != null)
itemsControl.GroupStyle.Add(#new);
}
}
}
The way I resolved this was create a new control that inherits from ListBox control that adds a bindable DefaultGroupStyle:
public class MyListBox : ListBox
{
public GroupStyle DefaultGroupStyle
{
get { return (GroupStyle)GetValue(DefaultGroupStyleProperty); }
set { SetValue(DefaultGroupStyleProperty, value); }
}
// Using a DependencyProperty as the backing store for DefaultGroupStyle. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DefaultGroupStyleProperty =
DependencyProperty.Register("DefaultGroupStyle", typeof(GroupStyle), typeof(MyListBox), new UIPropertyMetadata(null, DefaultGroupStyleChanged));
private static void DefaultGroupStyleChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
((MyListBox)o).SetDefaultGroupStyle(e.NewValue as GroupStyle);
}
private void SetDefaultGroupStyle(GroupStyle defaultStyle)
{
if (defaultStyle == null)
{
return;
}
if (this.GroupStyle.Count == 0)
{
this.GroupStyle.Add(defaultStyle);
}
}
}
Actually, with some extra work it can be done:
Instead of setting the template of ContexMenu to ItemsPresenter, you can set it to control that will fit you data. In this case , you can set it to Menu. Just like this:
<Style TargetType="ContextMenu">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ContextMenu">
<Border>
<Menu ItemsSource="{TemplateBinding ItemsSource}">
<Menu.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<StackPanel>
<Border Background="Black">
<ItemsPresenter/>
</Border>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</Menu.GroupStyle>
<Menu.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel></StackPanel>
</ItemsPanelTemplate>
</Menu.ItemsPanel>
</Menu>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Now, though the GroupStyle is readonly, we can set it through PropertyElement :-)
In order to get exactly the feel of MenuItem of ContexMenu you can adjust the style of MenuItem

WPF Ribbon - Data templating in a RibbonTab

I've spent the better part of the last two days dinging around with this control and I'm stuck. Basically I don't how to data template it's RibbonTab. What I have would work for me if it would only not show it at the bottom of the RibbonTab! Grr!
What I have in my XAML is:
<r:Ribbon Grid.Row="0" Title="MSRibbon" x:Name="ribbon">
<r:Ribbon.Style>
<Style TargetType="{x:Type r:Ribbon}">
<Setter Property="TabHeaderTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="{Binding Header}" HorizontalAlignment="Center" VerticalAlignment="Center" />
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemsSource" Value="{Binding AvailableRibbonTabs}"/>
<Setter Property="SelectedItem" Value="{Binding SelectedRibbonTab}"/>
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type r:RibbonTab}">
<Setter Property="ItemsSource" Value="{Binding RibbonTabData}"/>
</Style>
</Setter.Value>
</Setter>
</Style>
</r:Ribbon.Style>
<r:Ribbon.Resources>
<DataTemplate DataType="{x:Type vmsrc:RecordingRibbonTabGroupData}">
<viewsrc:RecordingTabGroupControl/>
</DataTemplate>
</r:Ribbon.Resources>
</r:Ribbon>
The XAML of the control i would like to show in the ribbon tab group is (this, when displayed gets glued to the bottom of the ribbon tab):
<r:RibbonControl x:Class="Scanner.Views.RecordingRibbonTabGroupData">
<StackPanel Orientation="Horizontal">
<r:RibbonButton Label="foo" />
<r:RibbonButton Label="bar" />
<ListBox ItemsSource="{Binding Barcodes}" />
</StackPanel>
</r:RibbonControl>
Here I tried using different combinations of controls but to no effect. As the control base type I used the RibbonTab, the RibbonGroup, UserControl etc and I think I used every possible control as the main container, like StackPanel, Grid, ItemsControl, etc.. And also experimented with setting the Heights of every control and H/V alignment, etc. Nothing helped.
My view models are such (INPC is injected with INPCWeaver and it works):
public abstract class AbstractViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
}
public abstract class AbstractRibbonTab : AbstractViewModel
{
public string Header { get; set; }
public bool IsSelected { get; set; }
public ObservableCollection<AbstractRibbonTabGroupData> RibbonTabData { get; set; }
}
public class RecordingRibbonTab : AbstractRibbonTab
{
public RecordingRibbonTab()
{
this.Header = "Recording";
this.RibbonTabData = new ObservableCollection<AbstractRibbonTabGroupData>() { new RecordingRibbonTabGroupData() };
}
}
public class SessionRibbonTab : AbstractRibbonTab
{
public SessionRibbonTab()
{
this.Header = "Session";
this.RibbonTabData = new ObservableCollection<AbstractRibbonTabGroupData>() { new AbstractRibbonTabGroupData() };
}
}
public class SettingsRibbonTab : AbstractRibbonTab
{
public SettingsRibbonTab()
{
this.Header = "Settings";
this.RibbonTabData = new ObservableCollection<AbstractRibbonTabGroupData>() { new AbstractRibbonTabGroupData() };
}
}
The XAML has it's data context set to an instance of:
public class MainWindowViewModel : AbstractViewModel, IMainWindowViewModel
{
...
public ObservableCollection<AbstractRibbonTab> AvailableRibbonTabs { get; private set; }
public AbstractRibbonTab SelectedRibbonTab { get; set; }
...
public MainWindowViewModel(PinChangeCommand pcc)
{
this.AvailableRibbonTabs = new ObservableCollection<AbstractRibbonTab>();
this.AvailableRibbonTabs.Add(new RecordingRibbonTab());
this.AvailableRibbonTabs.Add(new SessionRibbonTab());
this.AvailableRibbonTabs.Add(new SettingsRibbonTab());
}
}
The bindings work.
As a side note, below the ribbon there is a content control declared like so
<ContentControl Grid.Row="1" Content="{Binding SelectedRibbonTab}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type vmsr:RecordingRibbonTab}">
<views:RecordingView />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
that works perfectly fine as one would expect.
The 'recording' view that I did implement has the following XAML (it just shows the header, as one can see in the screenshot below):
<UserControl x:Class="Scanner.Views.RecordingView">
<Grid>
<TextBlock Text="{Binding Header}" />
</Grid>
</UserControl>
Wrapping up, a code listing that should explain some strange numbers:
public class RecordingRibbonTabGroupData : AbstractRibbonTabGroupData
{
public ObservableCollection<string> Barcodes { get; private set; }
public RecordingRibbonTabGroupData()
{
this.Barcodes = new ObservableCollection<string>();
this.Barcodes.Add("76765535642");
this.Barcodes.Add("43435356");
}
}
Without DataTemplate:
WITH DataTemplate:
What you need is two ItemContainerStyle
<Ribbon:Ribbon ItemContainerStyle="{StaticResource RibbonTabStyle}" ItemsSource="{Binding DummyRibbonTabContent}">
first:
<Style TargetType="{x:Type Ribbon:RibbonTab}" x:Key="RibbonTabStyle">
<Setter Property="ItemsSource" Value="{Binding DummyRibbonGroups}" />
<Setter Property="ItemContainerStyle" Value="{DynamicResource RibbonGroupStyle}" />
<Setter Property="Header" Value="{Binding DummyRibbonHeader"} />
</Style>
second:
<Style TargetType="{x:Type Ribbon:RibbonGroup}" x:Key="RibbonGroupStyle">
<Setter Property="Header" Value="{Binding RibbonGroupHeader}" />
<Setter Property="ItemsSource" Value="{Binding DummyRibbonButtons}" />
<Setter Property="ItemTemplate" Value="{DynamicResource RibbonButtonTemplate}" />
</Style>
Obviously you have to create a ribbon button datatemplate. You could also use item templateselector for the ribbongroupstyle and then you can add not just ribbonbuttons but whatever you wish. Its not the exact solution you need, but I hope you get the idea.

"Tag" ... Special functionality in WPF?

MSDN says "Gets or sets an arbitrary object value that can be used to store custom information about this element." which means I can store anything I want in this property.
But if you bind to this property (with property of type String having a value say "XYZ") and use it in Trigger conditions it doesn't work!
<Trigger Property="Tag" Value="XYZ">
<Setter Property="Background" Value="Red" />
</Trigger>
It does not set the background red. You can try and assume myElement to be a TextBlock! Why is it like this?
Tag has no special functionality in WPF.
This works for me:
<TextBlock Tag="{Binding Data}"
x:Name="tb">
<TextBlock.Style>
<Style>
<Style.Triggers>
<Trigger Property="TextBlock.Tag"
Value="XYZ">
<Setter Property="TextBlock.Background"
Value="Lime" />
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
And setting the Data object property to "XYZ" in an event.
The Tag is a construct held over from Winforms days (and possibly there from before that!). It was used as a convenient place to associate an object with a UI element, such as a FileInfo with a Button, so in the Button's event handler you could simply take the event sender, cast it to a Button, then cast the Tag value to a FileInfo and you have everything you need about the file you want to open.
There is one situation, however, where I've found the Tag is useful in WPF. I've used it as a holding spot that can be accessed by a ContextMenu MenuItem, which can't use the normal RelativeSource bindings you'd use to traverse the visual tree.
<ListBox.ItemContainerStyle>
<Style
TargetType="ListBoxItem">
<Setter
Property="Tag"
Value="{Binding ElementName=TheUserControlRootElement}" />
<Setter
Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem
Header="_Remove"
ToolTip="Remove this from this list"
CommandParameter="{Binding DataContext, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
Command="{Binding PlacementTarget.Tag.Remove, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
From the ContextMenu, I cannot access the Remove command which is defined in the UserControl class where this snippet is defined. But I can bind the root to the Tag of the ListBoxItem, which I can access via the ContextMenu.PlacementTarget property. The same trick can be used when binding within a ToolTip, as the same limitations apply.
MainWindow.xaml:
<Window x:Class="wpftest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBlock x:Name="test" MouseDown="test_MouseDown"
Tag="{Binding TestProperty}">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<Trigger Property="Tag" Value="XYZ">
<Setter Property="Background" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
</Window>
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new TestViewModel();
}
private void test_MouseDown(object sender, MouseButtonEventArgs e)
{
((TestViewModel)DataContext).TestProperty = "XYZ";
}
private sealed class TestViewModel : INotifyPropertyChanged
{
private string _testPropertyValue;
public string TestProperty
{
get { return _testPropertyValue; }
set
{
_testPropertyValue = value;
var handler = PropertyChanged;
if(handler != null)
handler(this, new PropertyChangedEventArgs("TestProperty"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
Updated: Tag property now is bound to TestProperty.

Resources