I've created a IconButton for use in WPF/XMAML. It should be able to display an Icon MDL2 Assets font on top and an text on bottom. It should have the appearance of an default WPF toolbar button. I decided to create a custom control which inherits from default WPF button.
So I created the custom control and added Dependency Properties for Text and the somehow cryptic MDL2IconCode:
public class IconButton : Button
{
public static readonly DependencyProperty TextProperty;
public static readonly DependencyProperty MDL2IconCodeProperty;
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public string MDL2IconCode
{
get { return (string)GetValue(MDL2IconCodeProperty); }
set { SetValue(MDL2IconCodeProperty, value); }
}
static IconButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(IconButton),
new FrameworkPropertyMetadata(typeof(IconButton)));
TextProperty = DependencyProperty.Register("Text",
typeof(string),
typeof(IconButton),
new PropertyMetadata("Button text", OnTextChanged));
MDL2IconCodeProperty = DependencyProperty.Register("MDL2IconCode",
typeof(string),
typeof(IconButton),
new PropertyMetadata("\uf13e", OnIconTextChanged));
}
static void OnTextChanged(DependencyObject o,
DependencyPropertyChangedEventArgs e)
{
var iconButton = o as IconButton;
if (iconButton == null)
{
return;
}
string newText = e.NewValue as string;
iconButton.Text = newText;
}
static void OnIconTextChanged(DependencyObject o,
DependencyPropertyChangedEventArgs e)
{
var iconButton = o as IconButton;
if (iconButton == null)
{
return;
}
string newText = e.NewValue as string;
iconButton.MDL2IconCode = newText;
}
}
The ResourceDictionary of Generic.xaml looks like this:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:UI.CustomControls">
<Style TargetType="{x:Type local:IconButton}"
BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:IconButton}">
<Button Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}">
<StackPanel>
<TextBlock HorizontalAlignment="Center"
Text="{TemplateBinding MDL2IconCode}"
FontFamily="Segoe MDL2 Assets"
FontSize="16"
x:Name="iconTextBlock"/>
<TextBlock HorizontalAlignment="Center"
Text="{TemplateBinding Text}"
x:Name="textTextBlock"/>
</StackPanel>
</Button>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
The button looks as it should.
But the command binding in XAML isn't working anymore. But it should work, as per inheritance it still is a button..
Maybe anyone has an idea what to add to make the command binding work?
Bind the command of the Button inside of your control template to the templated parent.
<ControlTemplate TargetType="{x:Type local:IconButton}">
<Button Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}"
Command="{TemplateBinding Command}"
CommandParameter="{TemplateBinding CommandParameter}"
CommandTarget="{TemplateBinding CommandTarget}">
<!-- ...other code. -->
</Button>
</ControlTemplate>
But it should work, as per inheritance it still is a button..
No. The Button inside of your control template does not magically bind to the corresponding properties of its templated parent, regardless if it is derived from Button or any other control. You will have to do so for other dependency properties like the CommandParameter as well.
Please also note that TemplateBinding is an optimized binding that does not have all capabilities of the more powerful Binding markup extension. Consequently, when TemplateBinding does not work, e.g. in two-way binding scenarios, you can use TemplatedParent like this:
{Binding RelativeSource={RelativeSource TemplatedParent}, Path=MyDependencyProperty}
For more information, you can refer to the TemplateBinding documentation.
The ControlTemplate of a Button shouldn't include another Button.
You should template your control to look like a Button if that's what you want:
<Style TargetType="{x:Type local:IconButton}" BasedOn="{StaticResource {x:Static ToolBar.ButtonStyleKey}}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:IconButton}">
<Border Name="Bd" Background="{TemplateBinding Control.Background}"
BorderBrush="{TemplateBinding Control.BorderBrush}"
BorderThickness="{TemplateBinding Control.BorderThickness}"
Padding="{TemplateBinding Control.Padding}" SnapsToDevicePixels="true">
<ContentControl HorizontalAlignment="{TemplateBinding Control.HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding Control.VerticalContentAlignment}"
SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}">
<StackPanel>
<TextBlock HorizontalAlignment="Center"
Text="{TemplateBinding MDL2IconCode}"
FontFamily="Segoe MDL2 Assets"
FontSize="16"
x:Name="iconTextBlock"/>
<TextBlock HorizontalAlignment="Center"
Text="{TemplateBinding Text}"
x:Name="textTextBlock"/>
</StackPanel>
</ContentControl>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="UIElement.IsMouseOver" Value="true">
<Setter TargetName="Bd" Value="#80DADADA" Property="BorderBrush"/>
<Setter TargetName="Bd" Value="#FFB6BDC5" Property="Background"/>
</Trigger>
<Trigger Property="UIElement.IsKeyboardFocused" Value="true">
<Setter TargetName="Bd" Value="#80DADADA" Property="BorderBrush"/>
<Setter TargetName="Bd" Value="#FFB6BDC5" Property="Background"/>
</Trigger>
<Trigger Property="ButtonBase.IsPressed" Value="true">
<Setter TargetName="Bd" Value="#90006CD9" Property="BorderBrush"/>
<Setter TargetName="Bd" Value="#400080FF" Property="Background"/>
</Trigger>
<Trigger Property="UIElement.IsEnabled" Value="false">
<Setter Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" Property="Foreground"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
It will then behave like any other Button which means that you can bind its Command property as usual:
<local:IconButton Command="{Binding YourCommand}" />
Related
I have a TemplatedControl SoftwareReleaseControl, which displays some texts and a button. I need this button to inherit its Click event from the property OnInstallClick that is specified when creating the SoftwareReleaseControl control.
The problem is: I can't make it work. It does not bind to the template's property. I've tried copying source code from Avalonia's button (ClickEvent) to the control's Code-Behind. It shows as an EventHandler, but is not passed to the button, and also gives an Unable to find suitable setter or adder [...] error.
SoftwareReleaseControl.xaml:
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="using:Updater.Controls">
<Design.PreviewWith>
<StackPanel Spacing="5">
<Panel Classes="Spacing"/>
<my:SoftwareReleaseControl Title="..." Version="..." Description="..." Installed="..."/>
<my:SoftwareReleaseControl Title="..." Version="..." Description="..." Installed="..."/>
</StackPanel>
</Design.PreviewWith>
<Style Selector="TextBlock">
<Setter Property="Foreground" Value="{DynamicResource text}"/>
<Setter Property="FontFamily" Value="Lato"/>
</Style>
[...]
<Style Selector="my|SoftwareReleaseControl">
[...]
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Panel Background="{TemplateBinding Background}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
Height="{TemplateBinding Height}"
MinWidth="{TemplateBinding MinWidth}" Width="{TemplateBinding Width}">
<Grid Margin="{TemplateBinding Padding}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
RowDefinitions="46, *, 40">
[...]
<StackPanel Grid.Row="2" Orientation="Horizontal">
======= HERE ===>> <Button x:Name="PART_footer_installButton"
Content="Instalar"
======= PROBLEM ===>> Click="{TemplateBinding OnInstallClick}">
<Button.Styles>
<Style Selector="Button#PART_footer_installButton">
<Setter Property="VerticalAlignment" Value="Bottom"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="Foreground" Value="{DynamicResource text}"/>
<Setter Property="FontSize" Value="16"/>
<Setter Property="Tag" Value="{TemplateBinding Tag}"/>
</Style>
<Style Selector="Button#PART_footer_installButton:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource text}"/>
</Style>
</Button.Styles>
</Button>
</StackPanel>
</Grid>
</Panel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Styles>
SoftwareReleaseControl.xaml.cs (code-behind):
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
using System;
namespace Updater.Controls
{
public partial class SoftwareReleaseControl : TemplatedControl
{
public SoftwareReleaseControl()
{
}
public static readonly RoutedEvent<RoutedEventArgs> OnInstallClickEvent = RoutedEvent.Register<Button, RoutedEventArgs>(nameof(OnInstallClick), RoutingStrategies.Bubble);
public event EventHandler<RoutedEventArgs> OnInstallClick
{
add => AddHandler(OnInstallClickEvent, value);
remove => RemoveHandler(OnInstallClickEvent, value);
}
[...]
}
}
MainWindow.xaml (where I'm trying to show the controls):
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:Updater.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:my="using:Updater.Controls"
mc:Ignorable="d" Width="800" Height="520"
x:Class="Updater.Views.MainWindow"
Icon="/Assets/avalonia-logo.ico"
Title="Updater">
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Background="{DynamicResource window.background}"
RowDefinitions="40, *" ColumnDefinitions="*">
[...]
<ScrollViewer Grid.Row="1">
<StackPanel Spacing="5" x:Name="stk_releases">
<Panel Classes="Spacing"/>
==== HERE ===>> <my:SoftwareReleaseControl Title="..." Version="..." Description="..." Installed="..." OnInstallClick="{Binding btn_OnClick}"/>
<my:SoftwareReleaseControl Title="..." Version="..." Description="..." Installed="..." OnInstallClick="{Binding btn_OnClick}"/>
</StackPanel>
</ScrollViewer>
</Grid>
</Window>
It doesn't really matter if I will need to implement btn_OnClick in the MainWindow's code-behind or in the ViewModel.
The current code is giving me the errors:
Unable to find suitable setter or adder for property OnInstallClick of type Updater:Updater.Controls.SoftwareReleaseControl for argument Avalonia.Markup:Avalonia.Data.Binding, available setter parameter lists are: System.EventHandler[[Avalonia.Interactivity.RoutedEventArgs, Avalonia.Interactivity]] on MainWindow.xaml, on the OnInstallClick="{Binding btn_OnClick}".
Unable to find suitable setter or adder for property Click of type Avalonia.Controls:Avalonia.Controls.Button for argument Avalonia.Base:Avalonia.Data.IBinding, available setter parameter lists are: System.EventHandler1<Avalonia.Interactivity.RoutedEventArgs> on *SoftwareReleaseControl.xaml*, on the Click="{TemplateBinding OnInstallClick}".
Yes, I did specify the StyleInclude on App.xaml.
Why Click and not Command: I need the sender object so I can get the Tag property of the button. There will be many of this control on the window and I need to sort out which one got clicked.
tl;dr: How can I specify an event handler on my templated control, in a way that the button inside it can inherit the handler as its Click (not Command). Where will I need to implement the handler? ViewModel or CodeBehind?
I gave up on using Click, and instead found a way to send the button itself to a Command.
SoftwareReleaseControl.xaml:
[...]
<Button x:Name="PART_footer_installButton"
Command="{Binding _OnInstallClick}"
CommandParameter="{Binding RelativeSource={RelativeSource Self}}">
<Button.Styles>
<Style Selector="Button#PART_footer_installButton">
<Setter Property="VerticalAlignment" Value="Bottom"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="Foreground" Value="{DynamicResource text}"/>
<Setter Property="FontSize" Value="16"/>
<Setter Property="Tag" Value="{TemplateBinding Tag}"/>
</Style>
<Style Selector="Button#PART_footer_installButton:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource text}"/>
</Style>
</Button.Styles>
</Button>
[...]
SoftwareReleaseControl.xaml.cs:
namespace Updater.Controls
{
public partial class SoftwareReleaseControl : TemplatedControl
{
public SoftwareReleaseControl()
{
DataContext = this;
}
public event EventHandler InstallClick;
private void _OnInstallClick(object? sender)
{
EventHandler handler = InstallClick;
handler?.Invoke(sender, EventArgs.Empty);
}
[...]
}
}
MainWindow.xaml.cs:
namespace Updater.Views
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel(this);
for (int i = 0; i < 6; i++)
{
var t = new SoftwareReleaseControl();
t.Description = (string)App.Current.Resources["lorem.50"];
t.Installed = i % 2 == 0;
t.Tag = i;
t.InstallClick += T_InstallClick;
stk_releases.Children.Add(t);
}
}
private void T_InstallClick(object? sender, EventArgs e)
{
Debug.WriteLine("123");
if (sender is Button btn)
{
Debug.WriteLine(btn.Tag);
}
}
}
}
I have a custom RadioButton style with an Image and 2 TextBlocks.
<Style x:Key="ToggleButton_Chose" TargetType="{x:Type ToggleButton}" >
<Setter Property="Background" Value="#32353B" />
<Setter Property="Margin" Value="10,5"/>
<Setter Property="Height" Value="45" />
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="Padding" Value="1" />
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<StackPanel>
<Border BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" Margin="0">
<ContentPresenter HorizontalAlignment="Left"
VerticalAlignment="Center"/>
<Image Source="{TemplateBinding Button.Tag}" HorizontalAlignment="Left" Stretch="Uniform" Width="45" IsEnabled="True" />
<StackPanel Margin="2">
<TextBlock Foreground="#DCDDDE" FontSize="18" FontFamily="Arial">Strona</TextBlock>
<TextBlock Foreground="#52555C" FontSize="12" FontFamily="Arial"> Login</TextBlock>
</StackPanel>
</StackPanel>
</Border>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Background" Value="#282B2E"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="#FF282B2E" Opacity="0.5"/>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
When I create a new button at runtime, setting the Style and I want to change the Image and the text for every single RadioButton. Right now I am thinking about using a Tag for that.
RadioButton radioButton = new RadioButton();
radioButton.GroupName = "Side";
radioButton.Style = (Style)Resources["ToggleButton_Chose"];
radioButton.Tag = new BitmapImage(new Uri("https://www.google.com/favicon.ico"));
Is there any other method to set this?
I will have around 100 of the RadioButtons and any of them should get different images and texts.
Custom Control
If you want to create a ToggleButton that requires additional (bindable) properties, you could create a custom control with dependency properties for the image and texts based on the ToggleButton type. Create a new type AdvancedToggleButton that derives from ToggleButton.
public class AdvancedToggleButton : ToggleButton
{
static AdvancedToggleButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(AdvancedToggleButton), new FrameworkPropertyMetadata(typeof(AdvancedToggleButton)));
}
public ImageSource ImageSource
{
get => (ImageSource) GetValue(ImageSourceProperty);
set => SetValue(ImageSourceProperty, value);
}
public static readonly DependencyProperty ImageSourceProperty = DependencyProperty.Register(
nameof(ImageSource), typeof(ImageSource), typeof(AdvancedToggleButton));
public string FirstText
{
get => (string) GetValue(FirstTextProperty);
set => SetValue(FirstTextProperty, value);
}
public static readonly DependencyProperty FirstTextProperty = DependencyProperty.Register(
nameof(FirstText), typeof(string), typeof(AdvancedToggleButton));
public string SecondText
{
get => (string) GetValue(SecondTextProperty);
set => SetValue(SecondTextProperty, value);
}
public static readonly DependencyProperty SecondTextProperty = DependencyProperty.Register(
nameof(SecondText), typeof(string), typeof(AdvancedToggleButton));
}
Then you can create a default implicit style (by omitting the x:Key) in your application resources or another resource dictionary in scope, so the style is applied automatically.
<Style TargetType="{x:Type local:AdvancedToggleButton}">
<Setter Property="Background"
Value="#32353B" />
<Setter Property="Margin"
Value="10,5" />
<Setter Property="Height"
Value="45" />
<Setter Property="BorderThickness"
Value="0" />
<Setter Property="Padding"
Value="1" />
<Setter Property="HorizontalAlignment"
Value="Stretch" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:AdvancedToggleButton}">
<StackPanel>
<Border BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}">
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Left"
Margin="0">
<ContentPresenter HorizontalAlignment="Left"
VerticalAlignment="Center" />
<Image Source="{TemplateBinding ImageSource}"
HorizontalAlignment="Left"
Stretch="Uniform"
Width="45"
IsEnabled="True" />
<StackPanel Margin="2">
<TextBlock Foreground="#DCDDDE"
FontSize="18"
FontFamily="Arial"
Text="{TemplateBinding FirstText}"/>
<TextBlock Foreground="#52555C"
FontSize="12"
FontFamily="Arial"
Text="{TemplateBinding SecondText}"/>
</StackPanel>
</StackPanel>
</Border>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsChecked"
Value="True">
<Setter Property="Background"
Value="#282B2E" />
</Trigger>
<Trigger Property="IsMouseOver"
Value="True">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="#FF282B2E"
Opacity="0.5" />
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
Note, that I removed the duplicate setter for HorizontalAlignment. The Image binds its Source to the ImageSource property and the TextBlocks bind to FirstText and SecondText respectively. You can either define the AdvancedToggleButton in XAML or code.
<local:AdvancedToggleButton ImageSource="\Resources/Check.jpg"
FirstText="Strona"
SecondText="Login"/>
RadioButton radioButton = new RadioButton();
radioButton.GroupName = "Side";
radioButton.ImageSource = new BitmapImage(new Uri("https://www.google.com/favicon.ico"));
radioButton.FirstText = "Strona";
radioButton.SecondText = "Login"
Useful resources on custom controls:
Control authoring overview
Dependency properties overview
Attached Properties
An alternative without creating a custom control is to create a set of attached properties.
public static class ToggleButtonProperties
{
public static ImageSource GetImageSource(DependencyObject dependencyObject)
{
return (ImageSource) dependencyObject.GetValue(ImageSourceProperty);
}
public static void SetImageSource(DependencyObject dependencyObject, ImageSource value)
{
dependencyObject.SetValue(ImageSourceProperty, value);
}
public static readonly DependencyProperty ImageSourceProperty = DependencyProperty.RegisterAttached(
"ImageSource", typeof(ImageSource), typeof(ToggleButtonProperties));
public static string GetFirstText(DependencyObject dependencyObject)
{
return (string) dependencyObject.GetValue(FirstTextProperty);
}
public static void SetFirstText(DependencyObject dependencyObject, string value)
{
dependencyObject.SetValue(FirstTextProperty, value);
}
public static readonly DependencyProperty FirstTextProperty = DependencyProperty.RegisterAttached(
"FirstText", typeof(string), typeof(ToggleButtonProperties));
public static string GetSecondText(DependencyObject dependencyObject)
{
return (string) dependencyObject.GetValue(SecondTextProperty);
}
public static void SetSecondText(DependencyObject dependencyObject, string value)
{
dependencyObject.SetValue(SecondTextProperty, value);
}
public static readonly DependencyProperty SecondTextProperty = DependencyProperty.RegisterAttached(
"SecondText", typeof(string), typeof(ToggleButtonProperties));
}
These properties can be bound in the control template using parentheses, which is the binding syntax for attached properties and RelativeSource to the parent ToggleButton.
<Style x:Key="ToggleButton_Chose" TargetType="{x:Type ToggleButton}">
<Setter Property="Background"
Value="#32353B" />
<Setter Property="Margin"
Value="10,5" />
<Setter Property="Height"
Value="45" />
<Setter Property="BorderThickness"
Value="0" />
<Setter Property="Padding"
Value="1" />
<Setter Property="HorizontalAlignment"
Value="Stretch" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<StackPanel>
<Border BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}">
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Left"
Margin="0">
<ContentPresenter HorizontalAlignment="Left"
VerticalAlignment="Center" />
<Image Source="{Binding (local:ToggleButtonProperties.ImageSource), RelativeSource={RelativeSource TemplatedParent}}"
HorizontalAlignment="Left"
Stretch="Uniform"
Width="45"
IsEnabled="True" />
<StackPanel Margin="2">
<TextBlock Foreground="#DCDDDE"
FontSize="18"
FontFamily="Arial"
Text="{Binding (local:ToggleButtonProperties.FirstText), RelativeSource={RelativeSource TemplatedParent}}"/>
<TextBlock Foreground="#52555C"
FontSize="12"
FontFamily="Arial"
Text="{Binding (local:ToggleButtonProperties.SecondText), RelativeSource={RelativeSource TemplatedParent}}"/>
</StackPanel>
</StackPanel>
</Border>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsChecked"
Value="True">
<Setter Property="Background"
Value="#282B2E" />
</Trigger>
<Trigger Property="IsMouseOver"
Value="True">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="#FF282B2E"
Opacity="0.5" />
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
Assigning or binding the attached properties is done via the static class.
<ToggleButton Style="{StaticResource ToggleButton_Chose}"
local:ToggleButtonProperties.ImageSource="\Resources/Check.jpg"
local:ToggleButtonProperties.FirstText="Strona"
local:ToggleButtonProperties.SecondText="Login"/>
RadioButton radioButton = new RadioButton();
radioButton.GroupName = "Side";
ToggleButtonProperties.SetImageSource(radioButton, new BitmapImage(new Uri("https://www.google.com/favicon.ico")));
ToggleButtonProperties.SetFirstText(radioButton, "Strona");
ToggleButtonProperties.SetSecondText(radioButton, "Login");
Useful resources on attached properties:
Attached Properties Overview
But is ther any other method to sett this? I will have around 100 of the RadioButtons and any of there should get other image and text.
Create a simple data type:
public class ButtonContent
{
public string Strona { get; set; }
public string Login { get; set; }
public object ImageSource { get; set; }
}
This type will be used to pass the button data into the context.
Therefore, we will slightly change the style:
<Style x:Key="ToggleButton_Chose" TargetType="{x:Type ToggleButton}" >
<Setter Property="Background" Value="#32353B" />
<Setter Property="Margin" Value="10,5"/>
<Setter Property="Height" Value="45" />
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="Padding" Value="1" />
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<StackPanel>
<Border BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" Margin="0">
<ContentPresenter HorizontalAlignment="Left"
VerticalAlignment="Center"/>
<Image Source="{Binding ImageSource}"
HorizontalAlignment="Left" Stretch="Uniform" Width="45" IsEnabled="True" />
<StackPanel Margin="2">
<TextBlock Foreground="#DCDDDE" FontSize="18" FontFamily="Arial"
Text="{Binding Strona}"/>
<TextBlock Foreground="#52555C" FontSize="12" FontFamily="Arial"
Text="{Binding Login}"/>
</StackPanel>
</StackPanel>
</Border>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Background" Value="#282B2E"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="#FF282B2E" Opacity="0.5"/>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
For a set of buttons use ItemsControl:
<DataTemplate x:Key="itemTemplate" DataType="{x:Type local:ButtonContent}">
<ToggleButton Style="{DynamicResource ToggleButton_Chose}"/>
</DataTemplate>
<ItemsControl x:Name="itemsControl"
ItemTemplate="{DynamicResource itemTemplate}">
</ItemsControl>
Create an observable collection with data and pass it to the ItemsControl source:
public partial class MainWindow : Window
{
private readonly ObservableCollection<ButtonContent> ButtonContents
= new ObservableCollection<ButtonContent>();
public MainWindow()
{
InitializeComponent();
itemsControl.ItemsSource = ButtonContents;
ButtonContents.Add(new ButtonContent() { Strona = "Strona1", Login = "Login1", ImageSource = "Image/block.png" });
ButtonContents.Add(new ButtonContent() { Strona = "Strona2", Login = "Login2", ImageSource = "Image/block.png" });
ButtonContents.Add(new ButtonContent() { Strona = "Strona3", Login = "Login3", ImageSource = "Image/block.png" });
ButtonContents.Add(new ButtonContent() { Strona = "Strona4", Login = "Login4", ImageSource = "Image/block.png" });
}
}
I've created a custom control called ActionButton inherited from Button. I've added a couple of dependency properties which work perfectly, however, I cannot get the binding to the Command property to work. When the application runs, the Command property always returns null.
Could someone please tell me what I'm doing wrong?
Here's some of the code which I hope should suffice.
// Custom control
public class ActionButton : Button
{
static ActionButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ActionButton), new FrameworkPropertyMetadata(typeof(ActionButton)));
}
// Some dependency properties go here
}
// In Generic.xaml
<Style TargetType="{x:Type controls:ActionButton}">
<Setter Property="Width" Value="48"/>
<Setter Property="Height" Value="48"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:ActionButton}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Border.ToolTip>
<StackPanel Margin="{DynamicResource ToolTipMargin}" MaxWidth="{DynamicResource MaxToolTipWidth}">
<TextBlock Style="{DynamicResource ToolTipHeaderStyle}" Text="{TemplateBinding ToolTipHeader}"/>
<Separator Visibility="Hidden"/>
<TextBlock Style="{DynamicResource ToolTipContentStyle}" Text="{TemplateBinding ToolTipText}"/>
</StackPanel>
</Border.ToolTip>
<Grid>
<Ellipse x:Name="BackEllipse" Stroke="{DynamicResource MahApps.Brushes.Accent}" StrokeThickness="0" Fill="{DynamicResource MahApps.Brushes.Accent}"/>
<Ellipse x:Name="FillEllipse" Stroke="{DynamicResource MahApps.Brushes.Accent}" StrokeThickness="3"/>
<TextBlock x:Name="BlockIconTextBox" Text="{TemplateBinding Icon, Converter={StaticResource FontIconConverter}}" FontSize="24" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="BackEllipse" Property="Opacity" Value="0.6"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="False">
<Setter TargetName="BackEllipse" Property="Opacity" Value="0.0"/>
<Setter TargetName="BlockIconTextBox" Property="Opacity" Value="0.6"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="FillEllipse" Property="Opacity" Value="0.3"/>
<Setter TargetName="BlockIconTextBox" Property="Opacity" Value="0.3"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
// View base class...
public class View : UserControl
{
public View()
{
ActionButtons = new ObservableCollection<ActionButton>();
}
public static readonly DependencyProperty ActionButtonsProperty = DependencyProperty.Register(nameof(ActionButtons), typeof(ObservableCollection<ActionButton>), typeof(View));
public ObservableCollection<ActionButton> ActionButtons
{
get => (ObservableCollection<ActionButton>)GetValue(ActionButtonsProperty);
set => SetValue(ActionButtonsProperty, value);
}
}
// Markup in a view...
<local:View
x:Class="Vesuvius.TeleCalc.Windows.Views.SettingsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Vesuvius.TeleCalc.Windows.Views"
xmlns:viewModels="clr-namespace:Vesuvius.TeleCalc.Windows.ViewModels"
xmlns:controls="clr-namespace:Vesuvius.TeleCalc.Windows.Controls"
xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls"
mc:Ignorable="d" d:DesignHeight="800" d:DesignWidth="1024"
Title="Settings"
>
<local:View.DataContext>
<viewModels:SettingsViewModel />
</local:View.DataContext>
<local:View.ActionButtons >
<!-- This is where things start to go wrong (I think) -->
<controls:ActionButton Icon="Color" ToolTipHeader="Reset theme" ToolTipText="Reset theme to default values." Command="{Binding ResetThemeCommand}"/>
</local:View.ActionButtons>
<!-- I have removed the rest for brevity -->
// In SettingsViewModel...
public SettingsViewModel()
{
ResetThemeCommand = CommandFactory.Create(ResetTheme);
}
public ICommand ResetThemeCommand { get; }
private void ResetTheme(object parameter)
{
// Do stuff here
}
// The issue...
public partial class SettingsView
{
public SettingsView()
{
InitializeComponent();
// The Command of SettingsViewModel.ResetThemeCommand is always null, so I have to resort to this nasty hack...
ActionButtons[0].Command = ((SettingsViewModel)DataContext).ResetThemeCommand;
// It's also worth noting, that the dependency properties ToolTipHeader and ToolTipText of the ResetThemeCommand are working properly.
}
}
I have a ListView with different itemTemplate, and each itemTemplates have an ExpanderDoubleClick : Expander inside
I would like to select items in extended Mode in a listview with custom selection design (declared in each items UserControl).
So here is my xaml for the listView:
<ListView x:Name="ListViewModules" ItemsSource="{Binding ListOfModules}"
ItemContainerStyle="{StaticResource ContainerListViewItemStyle}"
ItemTemplateSelector="{DynamicResource ModuleTemplateSelector}"
SelectionMode="Extended"
dd:DragDrop.IsDragSource="True"
dd:DragDrop.IsDropTarget="True"
dd:DragDrop.UseDefaultDragAdorner="True"
Grid.Column="0" Grid.Row="0" Height="494" Width="634" Background="#FFCDCDCD"
ScrollViewer.HorizontalScrollBarVisibility="Hidden" ScrollViewer.VerticalScrollBarVisibility="Visible"
BorderBrush="#666666" BorderThickness="1" Padding="0"
ClipToBounds="True" SnapsToDevicePixels="True" >
</ListView>
with styles for removing the standard blue selection (declared in App.xaml):
<Style x:Key="ContainerListViewItemStyle" TargetType="{x:Type ListViewItem}">
<Setter Property="Margin" Value="2,2,2,0"/>
<Setter Property="dd:DragDrop.DragSourceIgnore" Value="False"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<Border x:Name="Bd" BorderBrush="Transparent" BorderThickness="0" Background="Transparent" Padding="0" SnapsToDevicePixels="true">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I have different ItemTemplates with each "UserControl's" and here an exemple of one of them with it's selection design :
<UserControl x:Class="Topinambour.Templates.Modules.DelayTemplate" ...>
<Grid Width="602" MinHeight="24">
<Grid.ColumnDefinitions> <ColumnDefinition Width="40"/><ColumnDefinition /> </Grid.ColumnDefinitions>
<ToggleButton x:Name="TgBtIsActive" IsChecked="{Binding IsChecked}" IsThreeState="{Binding IsThreeState}" Grid.Column="0" Height="24" Width="40" click="TgBtIsActive_Click"/>
<templates:ExpanderDoubleClick x:Name="ModuleExpander" IsExpanded="{Binding IsExpanded, Mode=OneWay}" Height="auto" Template="{DynamicResource TemplateExpander}" Grid.Column="1" Margin="4,0,0,0" Collapsed="Expander_Collapsed" Expanded="Expander_Expanded" HorizontalContentAlignment="Center" VerticalContentAlignment="Center">
<templates:ExpanderDoubleClick.Resources>
<Style x:Key="ExpanderHeaderStyle" TargetType="{x:Type ToggleButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border x:Name="BorderIndent" BorderBrush="Gray" BorderThickness="1" Height="24" Width="558" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Padding="{Binding Indent}">
... my Header Content ...
</Border>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding IsExpanded}" Value="true">
<Setter Property="IsEnabled" TargetName="TbComment" Value="true"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=IsSelected, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ListViewItem}}" Value="true">
<Setter Property="Background" TargetName="headerCanvas" Value="#999999"/>
<Setter Property="BorderBrush" TargetName="TbComment" Value="#666666"/>
<Setter Property="Foreground" TargetName="LbHeaderTitle" Value="White"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</templates:ExpanderDoubleClick.Resources>
<Border Background="White" BorderBrush="#666666" BorderThickness="1,0,1,1" Height="136" Width="558">
<Grid>
... my Expanded Content ...
</Grid>
</Border>
</templates:ExpanderDoubleClick>
</Grid>
and here the ExpanderDoubleClick.cs :
public class ExpanderDoubleClick: Expander
{
private static readonly DependencyPropertyKey IsMouseDoubleClickedPropertyKey = DependencyProperty.RegisterReadOnly(
"IsMouseDoubleClicked",typeof(Boolean),typeof(ExpanderDoubleClick),new FrameworkPropertyMetadata(false));
public static readonly DependencyProperty IsMouseDoubleClickedProperty = IsMouseDoubleClickedPropertyKey.DependencyProperty;
public Boolean IsMouseDoubleClicked
{
get { return (Boolean)GetValue(IsMouseDoubleClickedProperty); }
}
static ExpanderDoubleClick()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ExpanderDoubleClick), new FrameworkPropertyMetadata(typeof(ExpanderDoubleClick)));
}
protected override void OnMouseDoubleClick(MouseButtonEventArgs e)
{
base.OnMouseDoubleClick(e);
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
ContentControl contentControl = base.GetTemplateChild("HeaderSite") as ContentControl;
if (contentControl != null)
{
contentControl.AddHandler(ContentControl.MouseDoubleClickEvent, new MouseButtonEventHandler(ExpanderHeader_MouseDoubleClick), true);
}
}
private void ExpanderHeader_MouseDoubleClick(Object sender, MouseButtonEventArgs e)
{
base.SetValue(IsMouseDoubleClickedPropertyKey, !IsMouseDoubleClicked);
base.IsExpanded= !base.IsExpanded;
}
}
the selection is not working when I click on the header of the expander but if I open it and click inside the expandedContent, it select the item ! what did I miss ? thank you for the reply.
I finally found a pretty good solution that keep my extended mode selection.
since the header seams to not receive Click event to handle.
I looked up the tree debug, and it shows that the click on header does not go through the "ListViewItem"
so I add an OnMouseLeftButtonDown Event on my border x.Name="BorderIdent" and then add RaisedEvent back to it's itemTemplate
like so :
private void BorderIndent_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
ListViewItem lvItem = UITools.FindAncestor<ListViewItem>(e.OriginalSource as DependencyObject);
bool isListViewItem = lvItem != null;
if (isListViewItem)
{
lvItem.RaiseEvent(e);
}
}
UITools.FindAncestor : is here
and all worked perfectly :)
<Style TargetType="controls:ModernVerticalMenu" >
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="controls:ModernVerticalMenu">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{TemplateBinding ListWidth}"/>
<ColumnDefinition Width="{TemplateBinding ListWidth}"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Border Background="{DynamicResource background}" Height="{TemplateBinding Height}" BorderThickness="1" BorderBrush="{DynamicResource bordaSuperior}">
<!-- link list -->
<ListBox x:Name="LinkList" ItemsSource="{TemplateBinding Links}"
ScrollViewer.HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}" >
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Height="50" Background="Transparent" Width="500">
<Border Padding="10">
<Path x:Name="icon" Data="{Binding IconData}" Stretch="Fill" Fill="{DynamicResource Accent}" Width="20" Height="20" HorizontalAlignment="Left" VerticalAlignment="Center" />
</Border>
<TextBlock x:Name="texto" ToolTip="{Binding Tooltip}" Text="{Binding DisplayName}" Margin="45,2,2,2" FontSize="{DynamicResource MediumFontSize}" TextTrimming="CharacterEllipsis" VerticalAlignment="Center" HorizontalAlignment="Left" />
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IconData}" Value="{x:Null}">
<Setter Property="Margin" TargetName="texto">
<Setter.Value>
<Thickness Bottom="2" Top="2" Left="10" Right="2"/>
</Setter.Value>
</Setter>
</DataTrigger>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Fill" TargetName="icon">
<Setter.Value>
<SolidColorBrush Color="#f2f2f2" />
</Setter.Value>
</Setter>
</Trigger>
</DataTemplate.Triggers>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I created a custom control and I am creating a template for a vertical menu, I wanna that in the event of MouseOver of the ListBox, I could set the value of proprierty of the controls:ModernVerticalMenu,
any idea?
When I write parent, is the ModerVerticalMenu and child is the ListBox from the ControlTemplate.
The main idea is to somehow get access to parent object and change its property. In order to do this I create one attached property retrieving access to parent by TemplatedParent markup, second attached property stores value which is supposed to be applied in parent's property. This sample is not precise your problem but on the right track, I believe.
I want to change button Content, which is initially Empty. First textblock reflects change in Button's Content to find out whether any occured.
<StackPanel>
<TextBlock Text="{Binding ElementName=but, Path=Content}"/>
<Button Name="but" Content="" Background="CadetBlue" BorderBrush="CadetBlue" BorderThickness="2">
<Button.Template>
<ControlTemplate TargetType="Button">
<TextBox Margin="50" Text="xD" local:MyClass.Parent="{Binding RelativeSource={RelativeSource AncestorType=Button}}">
<TextBox.Style>
<Style TargetType="TextBox">
<Setter Property="local:MyClass.Content" Value="{x:Null}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsMouseOver}" Value="True">
<Setter Property="Foreground" Value="CadetBlue"/>
<Setter Property="local:MyClass.Content" Value="tekst"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
</ControlTemplate>
</Button.Template>
</Button>
</StackPanel>
Class with attached properties looks as follows
public static class MyClass
{
public static void SetParent(DependencyObject obj, Button val)
{
obj.SetValue(ParentProperty, val);
}
public static Button GetParent(DependencyObject obj)
{
return (Button)obj.GetValue(ParentProperty);
}
public static readonly DependencyProperty ParentProperty = DependencyProperty.RegisterAttached("Parent", typeof(Button), typeof(MyClass), new PropertyMetadata(null, new PropertyChangedCallback(
(x, y) =>
{
Debug.WriteLine(GetParent(x));
})));
public static void SetContent(DependencyObject obj, string val)
{
obj.SetValue(ContentProperty, val);
}
public static string GetContent(DependencyObject obj)
{
return (string)obj.GetValue(ContentProperty);
}
public static readonly DependencyProperty ContentProperty = DependencyProperty.RegisterAttached("Content", typeof(string), typeof(MyClass), new PropertyMetadata(null, new PropertyChangedCallback(
(x, y) =>
{
if (GetContent(x) != String.Empty)
((Button)GetParent(x)).Content = GetContent(x);
})));
}
When mouse is over textbox then parent's property content changes to value of attached property set in setter
<Setter Property="local:MyClass.Content" Value="tekst"/>