Wpf Custom Control: How to implement CustomContent - c#

I'm working on a CustomControl in WPF. I want to Implement a CardControl. I have two Contents "Front" and "Back" and I want to flip between them.
But I can't define a Working Trigger in the Style to flip between the contents...
First I created a Custom Control, with DependencyProperties:
public class Card : Control {
#region initializer
static Card() {
DefaultStyleKeyProperty.OverrideMetadata(typeof(Card), new FrameworkPropertyMetadata(typeof(Card)));
}
#endregion
#region dependencyProperties
public FrameworkElement CustomContent {
get { return (FrameworkElement)GetValue(CustomContentProperty); }
set { SetValue(CustomContentProperty, value); }
}
public static DependencyProperty CustomContentProperty =
DependencyProperty.Register(nameof(CustomContent), typeof(FrameworkElement), typeof(Card), new PropertyMetadata());
public FrameworkElement Front {
get { return (FrameworkElement)GetValue(FrontProperty); }
set { SetValue(FrontProperty, value); }
}
public static DependencyProperty FrontProperty =
DependencyProperty.Register(nameof(Front), typeof(FrameworkElement), typeof(Card), new PropertyMetadata());
public FrameworkElement Back {
get { return (FrameworkElement)GetValue(BackProperty); }
set { SetValue(BackProperty, value); }
}
public static DependencyProperty BackProperty =
DependencyProperty.Register(nameof(Back), typeof(FrameworkElement), typeof(Card), new PropertyMetadata());
public bool IsDetailed {
get { return (bool)GetValue(IsDetailedProperty); }
set { SetValue(IsDetailedProperty, value); }
}
public static readonly DependencyProperty IsDetailedProperty =
DependencyProperty.Register(nameof(IsDetailed), typeof(bool), typeof(Card), new PropertyMetadata(false));
public CornerRadius CornerRadius {
get { return (CornerRadius)GetValue(CornerRadiusProperty); }
set { SetValue(CornerRadiusProperty, value); }
}
public static readonly DependencyProperty CornerRadiusProperty =
DependencyProperty.Register(nameof(CornerRadius), typeof(CornerRadius), typeof(Card));
#endregion
Then I defined the Style in Generic.xaml:
<Style TargetType="{x:Type local:Card}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:Card}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"
Content="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:Card}}, Path=CustomContent,
Converter={StaticResource Debugger}, UpdateSourceTrigger=PropertyChanged}"/>
</Border>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource
AncestorType={x:Type local:Card}},
Path=isDetailed,
Converter={StaticResource Debugger}}"
Value="True">
<Setter Property="CustomContent"
Value="{Binding RelativeSource={RelativeSource
AncestorType={x:Type local:Card}},
Path=Back}"/>
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource
AncestorType={x:Type local:Card}},
Path=isDetailed}"
Value="False">
<Setter Property="CustomContent"
Value="{Binding RelativeSource={RelativeSource
AncestorType={x:Type local:Card}},
Path=Front}"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And I Implemented a Simple CardControl for Test-Purposes with a Button, which flips the isDetailed state:
<StackPanel>
<CustomCharts:Card Name="TestCard">
<CustomCharts:Card.CustomContent>
<TextBlock Text="Hello World!"/>
</CustomCharts:Card.CustomContent>
<CustomCharts:Card.Front>
<TextBlock Text="Wow vordere sache!"/>
</CustomCharts:Card.Front>
<CustomCharts:Card.Back>
<TextBlock Text="Wow hintere sache!"/>
</CustomCharts:Card.Back>
</CustomCharts:Card>
<Button Click="Button_Click" Width="50" Height="20" Content="Test"/>
</StackPanel>
private void Button_Click( object sender, RoutedEventArgs e ) {
this.TestCard.IsDetailed= !this.TestCard.IsDetailed;
}
Any Help is apreciated, I'm stuck...

you can benefit a lot if declare Front and Back with object type, not FrameworkElement - mostly because it will allow easy bidnings. you can keep rich visual appearance if you add corresponding templates for those two properties.
public class Card : Control
{
static Card()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Card), new FrameworkPropertyMetadata(typeof(Card)));
}
public object Front
{
get { return (object)GetValue(FrontProperty); }
set { SetValue(FrontProperty, value); }
}
public static readonly DependencyProperty FrontProperty =
DependencyProperty.Register("Front", typeof(object), typeof(Card), new PropertyMetadata(null));
public DataTemplate FrontTemplate
{
get { return (DataTemplate)GetValue(FrontTemplateProperty); }
set { SetValue(FrontTemplateProperty, value); }
}
public static readonly DependencyProperty FrontTemplateProperty =
DependencyProperty.Register("FrontTemplate", typeof(DataTemplate), typeof(Card), new PropertyMetadata(null));
public object Back
{
get { return (object)GetValue(BackProperty); }
set { SetValue(BackProperty, value); }
}
public static readonly DependencyProperty BackProperty =
DependencyProperty.Register("Back", typeof(object), typeof(Card), new PropertyMetadata(null));
public DataTemplate BackTemplate
{
get { return (DataTemplate)GetValue(BackTemplateProperty); }
set { SetValue(BackTemplateProperty, value); }
}
public static readonly DependencyProperty BackTemplateProperty =
DependencyProperty.Register("BackTemplate", typeof(DataTemplate), typeof(Card), new PropertyMetadata(null));
public bool IsDetailed
{
get { return (bool)GetValue(IsDetailedProperty); }
set { SetValue(IsDetailedProperty, value); }
}
public static readonly DependencyProperty IsDetailedProperty =
DependencyProperty.Register(nameof(IsDetailed), typeof(bool), typeof(Card), new PropertyMetadata(false));
public CornerRadius CornerRadius
{
get { return (CornerRadius)GetValue(CornerRadiusProperty); }
set { SetValue(CornerRadiusProperty, value); }
}
public static readonly DependencyProperty CornerRadiusProperty =
DependencyProperty.Register(nameof(CornerRadius), typeof(CornerRadius), typeof(Card), new PropertyMetadata(new CornerRadius(0)));
}
Use Trigger on IsDetailed property to flip Fron and Back:
<Style TargetType="{x:Type local:Card}">
<Setter Property="Background" Value="Khaki"/>
<Setter Property="BorderBrush" Value="Black"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="CornerRadius" Value="10"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:Card}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<ContentPresenter x:Name="presenter"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsDetailed" Value="True">
<Setter TargetName="presenter" Property="Content"
Value="{Binding Front, RelativeSource={RelativeSource TemplatedParent}}"/>
<Setter TargetName="presenter" Property="ContentTemplate"
Value="{Binding FrontTemplate, RelativeSource={RelativeSource TemplatedParent}}"/>
</Trigger>
<Trigger Property="IsDetailed" Value="False">
<Setter TargetName="presenter" Property="Content"
Value="{Binding Back, RelativeSource={RelativeSource TemplatedParent}}"/>
<Setter TargetName="presenter" Property="ContentTemplate"
Value="{Binding BackTemplate, RelativeSource={RelativeSource TemplatedParent}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
use TemplateBindings inside Template, and RelativeSource TemplatedParent with normal Binding in Setters (TemplateBinding is not supported in Setter).
And here is two examples of usage - with and without binding:
<StackPanel DataContext="{x:Static sys:DateTime.Now}">
<CheckBox Name="chk_1"/>
<local:Card Front="Front"
Back="Back"
IsDetailed="{Binding IsChecked, ElementName=chk_1}"/>
<CheckBox Name="chk_2"/>
<local:Card Front="{Binding DayOfWeek}"
Back="{Binding TimeOfDay}"
IsDetailed="{Binding IsChecked, ElementName=chk_2}">
<local:Card.FrontTemplate>
<DataTemplate>
<TextBlock Foreground="Red" FontWeight="Bold" FontSize="20"
Text="{Binding}" TextDecorations="Underline"/>
</DataTemplate>
</local:Card.FrontTemplate>
<local:Card.BackTemplate>
<DataTemplate>
<TextBlock Foreground="Blue" FontWeight="Bold" FontSize="20"
Text="{Binding}" TextDecorations="Underline"/>
</DataTemplate>
</local:Card.BackTemplate>
</local:Card>
</StackPanel>
result:

Related

Binding update issue in WPF UserControl [duplicate]

This question already has answers here:
DependencyProperty not triggered
(2 answers)
What's the difference between Dependency Property SetValue() & SetCurrentValue()
(3 answers)
Closed 11 months ago.
I have a sets of user controls in my WPF application. One of them I've listed in code below:
RichSlider.xaml - control directly
<UserControl x:Name="RichSliderControl"
x:Class="CustomControls.RichSlider"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Controls;component/Themes/RichSlider.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
</UserControl>
RichSlider.xaml - control style
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:CustomControls="clr-namespace:CustomControls"
>
<Style x:Key="GlobalLabelStyle" TargetType="{x:Type FrameworkElement}">
<Setter Property="Margin" Value="0"/>
<Setter Property="TextElement.FontSize" Value="14"/>
<Setter Property="TextElement.FontWeight" Value="Bold"/>
<Setter Property="TextElement.FontFamily" Value="Century Gothic"/>
</Style>
<Style x:Key="ValueLabelStyle" TargetType="{x:Type Label}" BasedOn="{StaticResource GlobalLabelStyle}">
<Setter Property="Content" Value="{Binding Value, ElementName=RichSliderControl, Mode=OneWay}"/>
</Style>
<Style TargetType="{x:Type Slider}">
<Setter Property="Margin" Value="0"/>
<Setter Property="Foreground" Value="Gray"/>
<Setter Property="Background" Value="#F5F5F5"/>
<Setter Property="IsSnapToTickEnabled" Value="True"/>
<Setter Property="Minimum" Value="{Binding Minimum, ElementName=RichSliderControl}"/>
<Setter Property="Maximum" Value="{Binding Maximum, ElementName=RichSliderControl}"/>
<Setter Property="Value" Value="{Binding Value, ElementName=RichSliderControl}"/>
<Setter Property="SmallChange" Value="{Binding SmallChange, ElementName=RichSliderControl}"/>
<Setter Property="LargeChange" Value="{Binding LargeChange, ElementName=RichSliderControl}"/>
</Style>
<ControlTemplate x:Key="HorizontalSlider" TargetType="{x:Type UserControl}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" HorizontalAlignment="Left" VerticalContentAlignment="Center" Content="{Binding Text, ElementName=RichSliderControl, Mode=OneWay}"></Label>
<Label Grid.Row="0" Grid.Column="1" HorizontalAlignment="Right" VerticalContentAlignment="Center" Style="{StaticResource ValueLabelStyle}"></Label>
<Slider x:Name="ValueSlider"
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
Orientation="Horizontal"
TickPlacement="BottomRight"
>
</Slider>
</Grid>
</ControlTemplate>
<ControlTemplate x:Key="VerticalSlider" TargetType="{x:Type UserControl}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="26"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" HorizontalContentAlignment="Center" VerticalAlignment="Center" VerticalContentAlignment="Center">
<TextBlock TextWrapping="Wrap" VerticalAlignment="Center" Text="{Binding Text, ElementName=RichSliderControl, Mode=OneWay}"></TextBlock>
</Label>
<Label Grid.Row="1" Grid.Column="1" HorizontalContentAlignment="Center" Style="{StaticResource ValueLabelStyle}"></Label>
<Slider x:Name="ValueSlider"
Grid.Row="0"
Grid.Column="1"
Orientation="Vertical"
TickPlacement="TopLeft"
IsDirectionReversed="True"
>
</Slider>
</Grid>
</ControlTemplate>
<Style TargetType="{x:Type CustomControls:RichSlider}">
<Style.Triggers>
<Trigger Property="Orientation" Value="Horizontal">
<Setter Property="MinWidth" Value="100" />
<Setter Property="MinHeight" Value="20" />
<Setter Property="Template" Value="{StaticResource HorizontalSlider}" />
</Trigger>
<Trigger Property="Orientation" Value="Vertical">
<Setter Property="MinWidth" Value="20" />
<Setter Property="MinHeight" Value="100" />
<Setter Property="Template" Value="{StaticResource VerticalSlider}" />
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
RichSlider.xaml.cs - control code
public partial class RichSlider : UserControl
{
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(RichSlider));
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(double), typeof(RichSlider));
public static readonly DependencyProperty MinimumProperty =
DependencyProperty.Register("Minimum", typeof(double), typeof(RichSlider));
public static readonly DependencyProperty MaximumProperty =
DependencyProperty.Register("Maximum", typeof(double), typeof(RichSlider));
public static readonly DependencyProperty SmallChangeProperty =
DependencyProperty.Register("SmallChange", typeof(double), typeof(RichSlider));
public static readonly DependencyProperty LargeChangeProperty =
DependencyProperty.Register("LargeChange", typeof(double), typeof(RichSlider));
public static readonly DependencyProperty OrientationProperty =
DependencyProperty.Register("Orientation", typeof(Orientation), typeof(RichSlider));
private static readonly RoutedEvent ValueChangedEvent =
EventManager.RegisterRoutedEvent("ValueChanged", RoutingStrategy.Bubble, typeof(RoutedPropertyChangedEventHandler<double>), typeof(RichSlider));
[Bindable(true)]
public string Text
{
get => (string)GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
[Bindable(true)]
public double Value
{
get => (double)GetValue(ValueProperty);
set
{
var val = value;
if (val < Minimum)
{
val = Minimum;
}
if (val > Maximum)
{
val = Maximum;
}
SetValue(ValueProperty, val);
RaiseEvent(new(ValueChangedEvent));
}
}
[Bindable(true)]
public double Minimum
{
get => (double)GetValue(MinimumProperty);
set
{
var min = value;
if (Value < min)
{
Value = min;
}
if (Maximum < min)
{
Maximum = min;
}
SetValue(MinimumProperty, min);
}
}
[Bindable(true)]
public double Maximum
{
get => (double)GetValue(MaximumProperty);
set
{
var max = value;
if (Value > max)
{
Value = max;
}
if (max < Minimum)
{
max = Minimum;
}
SetValue(MaximumProperty, max);
}
}
[Bindable(true)]
public double SmallChange
{
get => (double)GetValue(SmallChangeProperty);
set
{
var change = value;
if (change > LargeChange)
{
change = LargeChange;
}
SetValue(SmallChangeProperty, change);
}
}
[Bindable(true)]
public double LargeChange
{
get => (double)GetValue(LargeChangeProperty);
set
{
var change = value;
if (change < SmallChange)
{
change = SmallChange;
}
SetValue(LargeChangeProperty, change);
}
}
[Bindable(true)]
public Orientation Orientation
{
get => (Orientation)GetValue(OrientationProperty);
set => SetValue(OrientationProperty, value);
}
public RichSlider()
{
InitializeComponent();
}
public event RoutedPropertyChangedEventHandler<double> ValueChanged
{
add
{
AddHandler(ValueChangedEvent, value);
}
remove
{
RemoveHandler(ValueChangedEvent, value);
}
}
public override void OnApplyTemplate()
{
if (GetTemplateChild("ValueSlider") is Slider slider)
{
slider.MouseWheel += new MouseWheelEventHandler(ValueSlider_MouseWheel);
slider.ValueChanged += new RoutedPropertyChangedEventHandler<double>(ValueSlider_ValueChanged);
}
base.OnApplyTemplate();
}
private void ValueSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
RaiseEvent(new(ValueChangedEvent));
}
private void ValueSlider_MouseWheel(object sender, MouseWheelEventArgs e)
{
var directionReversed = (sender as Slider).IsDirectionReversed;
if (((e.Delta > 0 && !directionReversed) || (e.Delta < 0 && directionReversed)) && Value < Maximum)
{
Value++;
}
else if (((e.Delta < 0 && !directionReversed) || (e.Delta > 0 && directionReversed)) && Value > Minimum)
{
Value--;
}
}
}
Ok. I'm adding this control to my window.xaml and binding some value like:
xmlns:CustomControls="clr-namespace:CustomControls;assembly=Controls"
....
<CustomControls:RichSlider Minimum="1" Maximum="5" SmallChange="1" Orientation="Horizontal" Value="{Binding Path=Acceleration, Mode=OneWay}" Text="{DynamicResource Acceleration}"></CustomControls:RichSlider>
And I have a ListBox of items, which contains Name and Acceleration properties. So, when I'm clicking on ListBoxItem, the value in my RichSlider is updating according to the value of Acceleration property in item. So, seems like the binding is working ok.
But no. When I'm changing the value in my RichSlider manually, the value is no longer updated for all other items and is stuck with the value I set. As evidence:
<TextBox Text="{Binding Path=Name, Mode=OneWay}"></TextBox>
The Text value is updating after selecting another item, not depends from changing I made. I also checked the way like:
<TextBox Text="{Binding Path=Acceleration, Mode=OneWay}"></TextBox>
and it's also works fine. So, this way I made the conclusion that the issue is in my control, not in Binding, cause for TextBox it works, but for RichSlider - not.
So, does anyone have any thoughts what I'm doing wrong?

Attached properties in Template WPF XAML

I have Attached Property and ControlTemplate using the attached property.
Attached Propery:
using System.Windows;
namespace NoteProjectV2.classes.frmtopicclasses
{
class togglebuttonimage : DependencyObject
{
public static readonly DependencyProperty togglebuttonimagesource = DependencyProperty.RegisterAttached("ImageSource", typeof(string), typeof(togglebuttonimage), new PropertyMetadata(default(string)));
public static void Settogglebuttonimagesource(UIElement element, string value)
{
element.SetValue(togglebuttonimagesource, value);
}
public static string Gettogglebuttonimagesource(UIElement element)
{
return (string)element.GetValue(togglebuttonimagesource);
}
}
}
This is my control template (I used this in togglebutton)
<Application x:Class="NoteProjectV2.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:NoteProjectV2"
xmlns:m="clr-namespace:NoteProjectV2.classes.frmtopicclasses"
StartupUri="frmTopic.xaml">
<Application.Resources>
<Style x:Key="togglebutton_topic_menu_normal" TargetType="ToggleButton">
<Setter Property="Width" Value="40" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Border Name="border">
<Border.Style>
<Style>
<Setter Property="Border.Background" Value="Black"/>
</Style>
</Border.Style>
<Image Width="22" Height="22" Name="image" >
<Image.Style>
<Style>
Only This is not working===============> <Setter Property="Image.Source" Value="{Binding Path=(m:togglebuttonimage.togglebuttonimagesource),RelativeSource={RelativeSource TemplatedParent}}" />
</Style>
</Image.Style>
</Image>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Application.Resources>
</Application>
I used attached propery in code:I also added namespace above
xmlns:m="clr-namespace:NoteProjectV2.classes.frmtopicclasses"
<ToggleButton Style="{StaticResource togglebutton_topic_menu_normal}" m:togglebuttonimage.togglebuttonimagesource="accept.png" />
But this is not working Where is my problem?
The first argument to the Register and RegisterAttached method of class DependencyProperty is the name of the property.
While you are using the name "ImageSource", it should actually be "ToggleButtonImageSource" (which already uses proper casing). Note also that as long as you only declare attached properties, the owning class does not need to be derived from DependencyObject.
public class ToggleButtonImage
{
public static readonly DependencyProperty ToggleButtonImageSourceProperty =
DependencyProperty.RegisterAttached(
"ToggleButtonImageSource", typeof(string), typeof(ToggleButtonImage));
public static void SetToggleButtonImageSource(UIElement element, string value)
{
element.SetValue(ToggleButtonImageSourceProperty, value);
}
public static string GetToggleButtonImageSource(UIElement element)
{
return (string)element.GetValue(ToggleButtonImageSourceProperty);
}
}
Besides that, you should better use ImageSource instead of string as the type of the property.

Style Trigger on sub-property of a DependencyProperty

I have a control I want to style depending on the set MessageType of a custom Message type DependencyProperty of that control.
Custom Type:
public class Message : ObservableObject
{
public MessageTypes MessageType
{
get { return _messageType; }
set {
RaisePropertyChanged(() => MessageType);
_messageType = value;
}
}
public string Text { ... }
...
}
Control:
public class MessageControl : Control
{
public static readonly DependencyProperty MessageProperty =
DependencyProperty.Register(
"Message",
typeof(Message),
typeof(MessageControl),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public Message Message
{
get
{
return (Message)GetValue(MessageProperty);
}
set
{
SetValue(MessageProperty, value);
}
}
}
Style:
<ControlTemplate x:Key="MessageControlTemplate"
TargetType="controls:MessageControl">
<Border Background="{TemplateBinding Background}">
<TextBlock Text="{Binding Path=Message.Text,
RelativeSource={RelativeSource TemplatedParent}}" />
</Border>
</ControlTemplate>
<Style TargetType="controls:MessageControl">
<Setter Property="Template"
Value="{StaticResource MessageControlTemplate}" />
<Style.Triggers>
<!-- HERE IS THE ISSUE -->
<Trigger Property="Message.MessageType"
Value="{x:Static classes:MessageType.Error}">
<Setter Property="Background" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
So the problem is that I can't add a Trigger for Message.MessageType (or basically any other sub property).
Is it even possible to accomplish this? Or should I just create two properties in MessageControl for Text and MessageType?
You could do it with a DataTrigger:
<Style TargetType="controls:MessageControl">
...
<Style.Triggers>
<DataTrigger Binding="{Binding Message.MessageType,
RelativeSource={RelativeSource Self}}"
Value="{x:Static classes:MessageType.Error}">
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
You should also raise the PropertyChanged event after setting the property's backing field:
public MessageType MessageType
{
get { return _messageType; }
set
{
_messageType = value;
RaisePropertyChanged(() => MessageType);
}
}

Position a button after the last listviewitem

My ListView is bound to an ObservableCollection, is there a way to position a button after the last listviewitem? What I have done is define the button in the DataTemplate like below:
<DataTemplate x:Key="TestDataTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="SeletedFilterText" Text="{Binding}" />
<Button Command="{Binding DataContext.TestCommand,ElementName=TestListView}"
Content="Test"
Visibility="{Binding Converter={StaticResource testConverter}}"
Grid.Column="1"/>
</Grid>
</DataTemplate>
In my ViewModel, I define a string variable to store the last item. The ItemSource(an Observable) may add or remove item, every time I set the last of the Collection to the LastItem variable. In the converter, compare the binding content with the LastItem, if the value is true, display the Button, if false, hide it. But the converter will never be triggered. Anyone can help?
I would suggest not to have backup field in ViewModel to keep track of lastItem in collection.
You can do that with only Converter in place which will return true or false if passed ListViewItem is last item in ListView or not.
In case you want to call the converter whenever underlying ObservableCollection add/remove item in it, I would suggest to pass Count property to converter so that converter gets fired whenever item is added/removed from collection.
Converter code:
public class IsLastItemInContainerConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter,
CultureInfo culture)
{
DependencyObject item = (DependencyObject)values[0];
ItemsControl ic = ItemsControl.ItemsControlFromItemContainer(item);
if (ic != null)
{
return ic.ItemContainerGenerator.IndexFromContainer(item)
== ic.Items.Count - 1;
}
else
return false;
}
public object[] ConvertBack(object value, Type[] targetTypes,
object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
XAML:
<Button Content="Test">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding
Converter="{StaticResource IsLastItemInContainerConverter}">
<Binding Path="."
RelativeSource="{RelativeSource Mode=FindAncestor,
AncestorType=ListViewItem}"/>
<Binding Path="DataContext.SourceCollection.Count"
RelativeSource="{RelativeSource Mode=FindAncestor,
AncestorType=ListView}"/>
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
Replace SourceCollection with your ObservableCollection name in dataTrigger.
I would suggest you create a custom control for your use case. Like so:
public class ButtonListView : ListView
{
static ButtonListView()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ButtonListView), new FrameworkPropertyMetadata(typeof(ButtonListView)));
}
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register(
"Command", typeof (ICommand), typeof (ButtonListView), new PropertyMetadata(default(ICommand)));
public ICommand Command
{
get { return (ICommand) GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public static readonly DependencyProperty ButtonContentProperty = DependencyProperty.Register(
"ButtonContent", typeof (object), typeof (ButtonListView), new PropertyMetadata(default(object)));
public object ButtonContent
{
get { return (object) GetValue(ButtonContentProperty); }
set { SetValue(ButtonContentProperty, value); }
}
}
And use this style:
<SolidColorBrush x:Key="ListBox.Disabled.Background" Color="#FFFFFFFF" />
<SolidColorBrush x:Key="ListBox.Disabled.Border" Color="#FFD9D9D9" />
<Style TargetType="{x:Type local:ButtonListView}" BasedOn="{StaticResource {x:Type ListBox}}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ButtonListView}">
<Border Name="Bd"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
SnapsToDevicePixels="true"
Padding="1">
<ScrollViewer Padding="{TemplateBinding Padding}"
Focusable="false">
<StackPanel>
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
<Button Content="{TemplateBinding ButtonContent}" Command="{TemplateBinding Command}"></Button>
</StackPanel>
</ScrollViewer>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter TargetName="Bd" Property="Background" Value="{StaticResource ListBox.Disabled.Background}" />
<Setter TargetName="Bd" Property="BorderBrush" Value="{StaticResource ListBox.Disabled.Border}" />
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsGrouping" Value="true" />
<Condition Property="VirtualizingPanel.IsVirtualizingWhenGrouping" Value="false" />
</MultiTrigger.Conditions>
<Setter Property="ScrollViewer.CanContentScroll" Value="false"/>
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
You can then use it like so:
<wpfSandbox:ButtonListView ButtonContent="Press" Command="{Binding ...}"/>
This way you don't need to Keep track of the order in the ObservableCollection
Yes and it's very easy:
Define your Observable collection with generic type of <DependencyObject>
Add a custom object to the end of the collection. (it can be a something like a ViewModel for Button if you want to add commands or etc to it)
Don't set the ItemTemplate of your ListView (or ItemsControl or etc)
Instead, define two DataTemplates without x:Key in the resources and set their DataType to the desired types. It should be like "{x:Type local:ButtonVm}" or "{x:Type vm:ListViewItemType}"
Now the template for each item automatically set to the data template that matches the type of that item.
Example:
(note that you can move ListView.Resources to Window.Resources if the templates can be reused elsewhere)
MainWindow.xaml:
<ListView ItemsSource="{Binding Items}">
<ListView.Resources>
<DataTemplate DataType="{x:Type vm:ListItemVm}">
<TextBlock Text="{Binding ItemText}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:ButtonVm}">
<Button Command="{Binding ButtonCommand}">
<TextBlock Text="{Binding ButtonText}"/>
</Button>
</DataTemplate>
</ListView.Resources>
</ListView>
MainWindow.xaml.cs:
public MainWindow()
{
InitializeComponent();
DataContext = this;
Items.Add(new ListItemVm { ItemText = "something" } );
Items.Add(new ListItemVm { ItemText = "something" } );
Items.Add(new ListItemVm { ItemText = "something" } );
Items.Add(new ButtonVm { ButtonText = "click here" } );
}
private ObservableCollection<DependencyObject> _items = new ObservableCollection<DependencyObject>();
public ObservableCollection<DependencyObject> Items { get { return _items; } }
one viewModel for each type of item:
public class ListItemVm : DependencyObject
{
public string ItemText
{
get { return (string)GetValue(ItemTextProperty); }
set { SetValue(ItemTextProperty, value); }
}
public static readonly DependencyProperty ItemTextProperty =
DependencyProperty.Register("ItemText", typeof(string), typeof(ListItemVm), new UIPropertyMetadata(""));
}
public class ButtonVm : DependencyObject
{
public string ButtonText
{
get { return (string)GetValue(ButtonTextProperty); }
set { SetValue(ButtonTextProperty, value); }
}
public static readonly DependencyProperty ButtonTextProperty =
DependencyProperty.Register("ButtonText", typeof(string), typeof(ButtonVm), new UIPropertyMetadata(""));
public Command ButtonCommand
{
get { return (string)GetValue(ButtonCommandProperty); }
set { SetValue(ButtonCommandProperty, value); }
}
public static readonly DependencyProperty ButtonCommandProperty =
DependencyProperty.Register("ButtonCommand", typeof(Command), typeof(ButtonVm), new UIPropertyMetadata(""));
}
public class Command : ICommand { /* simple implementation of ICommand */ }

Converting a UserControl to a Custom Control

The UserControl below works well but I would like to make it easier to change the Style.
One thing I have tried is to convert this to a Custom Control, but I am stuck on basics like how to set the ToolTip inside the static method that deals with a change in a property (see below)
The other thing I tried to move the Style into a generic button style in the ResourceDictionary, but that is the subject of this question
How can I set the ToolTip in my subclassed Button?
Cheers
UserControl XAML:
<UserControl.Resources>
<ResourceDictionary Source="pack://application:,,,/Smack.Core.Presentation.Wpf;component/Themes/generic.xaml" />
</UserControl.Resources>
<Button x:Name="_button" Style="{StaticResource blueButtonStyle}" Command="{Binding AddNewItemCommand}" >
<StackPanel Orientation="Horizontal" >
<Image Source="{resx:Resx ResxName=Smack.Core.Presentation.Resources.MasterDetail, Key=bullet_add}" Stretch="Uniform" VerticalAlignment="Center" />
<AccessText x:Name="_accesText" VerticalAlignment="Center">_Add New Subject</AccessText>
<ContentPresenter/>
</StackPanel>
</Button>
UserControl Code Behind:
public partial class AddNewItemButton : UserControl
{
public AddNewItemButton() { InitializeComponent(); }
public static readonly DependencyProperty SubjectProperty = DependencyProperty.Register(
"Subject", typeof (string), typeof (AddNewItemButton),
new FrameworkPropertyMetadata(OnSubjectChanged));
public string Subject { get { return (string) GetValue(SubjectProperty); } set { SetValue(SubjectProperty, value); } }
private static void OnSubjectChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) {
var control = obj as AddNewItemButton;
if (control == null) return;
control._accesText.Text = "_" + string.Format(MasterDetail.Subject_AddNew_Label, control.Subject.Capitalize());
control._button.ToolTip = string.Format(MasterDetail.Subject_AddNew_ToolTip, control.Subject.ToLower());
}
}
Failed attempt to create a Custom Control:
public class MyButton : Button
{
public static readonly DependencyProperty SubjectProperty = DependencyProperty.Register(
"ItemName", typeof(string), typeof(MyButton),
new FrameworkPropertyMetadata(OnSubjectChanged));
public string Subject
{
get { return (string)GetValue(SubjectProperty); }
set { SetValue(SubjectProperty, value); }
}
private static void OnSubjectChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
var control = obj as MyButton;
if (control == null) return;
ToolTip = ??;
}
}
UPDATE
Based on Phil's answer, the control (the bottom one) is more 'lookless' than I'd like :--)
Result
Code
public class AddNewItemButton : Button
{
static AddNewItemButton() {
var type = typeof (AddNewItemButton);
DefaultStyleKeyProperty.OverrideMetadata(type, new FrameworkPropertyMetadata(type));
}
#region Subject
public static readonly DependencyProperty SubjectProperty = DependencyProperty.Register(
"Subject", typeof(string), typeof(AddNewItemButton),
new PropertyMetadata(default(string)));
public string Subject
{
get { return (string)GetValue(SubjectProperty); }
set { SetValue(SubjectProperty, value); }
}
#endregion
}
Generic.xaml
<Style TargetType="{x:Type local:AddNewItemButton}">
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=Subject, Converter={StaticResource AddNewItemForToolTip}}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:AddNewItemButton}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Image Stretch="Uniform" VerticalAlignment="Center"
Source="{resx:Resx ResxName=Smack.Core.Presentation.Resources.MasterDetail, Key=bullet_add}" />
<AccessText
Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=Subject, Converter={StaticResource AddNewItemForLabel}}" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Here's an example of a custom button with a tooltip (based on the questions you've been asking recently):
This is the code
public class CustomButton : Button
{
static CustomButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomButton),
new FrameworkPropertyMetadata(typeof(CustomButton)));
}
public static readonly DependencyProperty SubjectProperty =
DependencyProperty.Register("Subject", typeof (string),
typeof (CustomButton), new PropertyMetadata(default(string)));
public string Subject
{
get { return (string) GetValue(SubjectProperty); }
set { SetValue(SubjectProperty, value); }
}
}
This goes in Themes/generic.xaml
<System:String x:Key="Test">Add new: </System:String>
<Style TargetType="{x:Type local:CustomButton}">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self}, Path=Subject}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomButton}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="Image here"
VerticalAlignment="Center" Padding="0,0,5,0"/>
<AccessText Grid.Column="1" VerticalAlignment="Center">
<AccessText.Text>
<MultiBinding StringFormat="{}_{0} {1}">
<Binding Source="{StaticResource Test}"/>
<Binding RelativeSource=
"{RelativeSource TemplatedParent}"
Path="Subject"/>
</MultiBinding>
</AccessText.Text>
</AccessText>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

Categories