Set GroupStyle inside style on xaml - wpf

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

Related

Template binding inside a ResourceDictionary Control Template

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}" />

How to override base style control template in derived style in WPF?

I have a style for button. That style contains the ControlTemplate for Button. The ControlTemplate contains an Image with name "ImgButton".
I want to make this style as base style for other Buttons and want to override the "Source" property of Image control for different buttons.
Any ideas?
You may create attached behavior that will offer a property to assign Source. You should bind your image to this property in a template using TemplatedParent as RelativeSource. In derived styles you can simply use Setter(s) to specify a different Source.
Attached behavoir:
public static class ImageSourceBehavior
{
public static readonly DependencyProperty SourceProperty = DependencyProperty.RegisterAttached(
"Source", typeof(ImageSource), typeof(ImageSourceBehavior),
new FrameworkPropertyMetadata(null));
public static ImageSource GetSource(DependencyObject dependencyObject)
{
return (ImageSource)dependencyObject.GetValue(SourceProperty);
}
public static void SetSource(DependencyObject dependencyObject, ImageSource value)
{
dependencyObject.SetValue(SourceProperty, value);
}
}
Styles:
<Style x:Key="Style1"
TargetType="Button">
<Setter Property="local:ImageSourceBehavior.Source"
Value="..."/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Image Source="{Binding Path=(local:ImageSourceBehavior.Source),RelativeSource={RelativeSource TemplatedParent}}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="Style2"
BasedOn="{StaticResource Style1}"
TargetType="Button">
<Setter Property="local:ImageSourceBehavior.Source"
Value="..."/>
</Style>

Silverlight element to element binding in generic.xaml

Is there a way to have element to element binding in Silverlight templated controls?
Example: I have two custom controls, SomeControl and CustomSlider. SomeControl has a dependency property called someValue. I want to bind the value of CustomSlider to this property, so my generic.xaml file looks like this:
<Style TargetType="local:SomeControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:SomeControl">
<...>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="local:CustomSlider">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:CustomSlider">
<Slider Value="{Binding someValue, ElementName=local:SomeControl}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
and this is my dependency property:
public int someValue,
{
get { return (int)GetValue(someValueProperty); }
set { SetValue(someValueProperty, value); }
}
public static readonly DependencyProperty (someValueProperty) =
DependencyProperty.Register(someValue); typeof(int), typeof(SomeControl,
new PropertyMetadata(0));
This throws an "BindingExpression_CannotFindElementName" exception.
You can't use it like this. A binding through ElementName should be used to specific element instance, not style. You can create other dependency property, say SliderValue in your CustomSlidercontrol and bind to it.
<local:SomeControl x:Name="SomeControl"/>
<local:CustomSlider SliderValue="{Binding someValue, ElementName=SomeControl}"/>
And change your Slider Value from template when your SliderValue property changes;

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.

Bind to a TemplatedParent's name in WPF?

I'm trying to create a WPF Custom Control which will bind itself to it's templated parent's x:Name property. The code which I believe should do this which was also generated by the Binding Maker in Visual Studio, is as follows:
Binding RelativeSource={RelativeSource TemplatedParent}, Path=Name, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay
But this produces no result. I can replace the binding with any plain text and the desired behavior (the text overlays itself on the control when and only when the user has typed nothing into the control), but my goal here is to make this work as a sort of tooltip so the user knows what the field is supposed to be (as defined in the field's x:Name property).
Here's my full xaml:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SuperTB">
<Style TargetType="{x:Type local:SuperTextB}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:SuperTextB}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<TextBox Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Text, Mode=TwoWay, UpdateSourceTrigger=LostFocus}">
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<Trigger Property="Text" Value="">
<Setter Property="Background">
<Setter.Value>
<VisualBrush Stretch="None">
<VisualBrush.Visual>
<TextBlock Foreground="Gray" FontSize="24" Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsRequired}"></TextBlock>
</VisualBrush.Visual>
</VisualBrush>
</Setter.Value>
</Setter>
</Trigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsRequired}" Value="True">
<Setter Property="BorderThickness" Value="4" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And the control's c#:
public class SuperTextB : Control
{
static SuperTextB()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(SuperTextB), new FrameworkPropertyMetadata(typeof(SuperTextB)));
}
public static DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(String), typeof(SuperTextB));
public string Text
{
get { return (String)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
private static DependencyProperty myNameProperty =
DependencyProperty.Register("MyName", typeof(string), typeof(SuperTextB), new PropertyMetadata("Unicorns !", NameChanged));
private static void NameChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
public string MyName
{
get { return (string)GetValue(myNameProperty); }
set { SetValue(myNameProperty, value); }
}
DependencyProperty isRequiredProperty =
DependencyProperty.Register("IsRequired", typeof(bool), typeof(SuperTextB), new PropertyMetadata(false, IsReqChanged));
private static void IsReqChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
public bool IsRequired
{
get { return (bool)GetValue(isRequiredProperty); }
set { SetValue(isRequiredProperty, value); }
}
}

Resources