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);
}
}
}
}
Related
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}" />
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.
}
}
This is my Style in App.xaml:
<Style x:Key="numButton" TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Image x:Name="img" Style="{DynamicResource imgDefault}"></Image>
<ControlTemplate.Triggers>
<Trigger Property="IsPressed" Value="true">
<Setter Property="Source" TargetName="img" Value="img/1_push.png"></Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="Cursor" Value="Hand"></Setter>
</Style>
<Style x:Key="imgDefault" TargetType="{x:Type Image}">
<Setter Property="Source" Value="img/0.png"></Setter>
</Style>
I have multiple buttons as numbers (0-9) and I want to use one style for all of the to avoid more text typing. Currently I am able to change each button's default background like this:
<Button Name="btn1" Grid.Row="0" Grid.Column="0" Style="{DynamicResource numButton}" Foreground="White">
<Button.Resources>
<Style x:Key="imgDefault" TargetType="{x:Type Image}">
<Setter Property="Source" Value="img/1.png"></Setter>
</Style>
</Button.Resources>
</Button>
So now I want to know is it possible to change Value="img/1_push.png" in App.xaml for each button when it's pressed? For example when 2 is pressed I want it's background to be 2_push.png.
Thanks in advance.
You could create custom Button class with two dependency properties:
ImageButton.cs:
namespace WpfApplication2
{
public class ImageButton : System.Windows.Controls.Button
{
public static readonly DependencyProperty DefaultImageProperty =
DependencyProperty.Register("DefaultImage", typeof(Uri), typeof(ImageButton));
public Uri DefaultImage
{
get { return (Uri)GetValue(DefaultImageProperty); }
set { SetValue(DefaultImageProperty, value); }
}
public static readonly DependencyProperty PressedImageProperty =
DependencyProperty.Register("PressedImage", typeof(Uri), typeof(ImageButton));
public Uri PressedImage
{
get { return (Uri)GetValue(PressedImageProperty); }
set { SetValue(PressedImageProperty, value); }
}
}
}
App.xaml:
<Application x:Class="WpfApplication2.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication2"
StartupUri="Window1.xaml">
<Application.Resources>
<Style TargetType="local:ImageButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ImageButton}">
<Image x:Name="img" Source="{Binding DefaultImage,RelativeSource={RelativeSource TemplatedParent}, FallbackValue=img/0.png, TargetNullValue=img/0.png}" />
<ControlTemplate.Triggers>
<Trigger Property="IsPressed" Value="true">
<Setter Property="Source" TargetName="img" Value="{Binding PressedImage, RelativeSource={RelativeSource TemplatedParent}, FallbackValue=img/1_push.png, TargetNullValue=img/1_push.png}"></Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="Cursor" Value="Hand"></Setter>
</Style>
</Application.Resources>
Usage:
<local:ImageButton x:Name="btn1" Grid.Row="0" Grid.Column="0" Foreground="White"
DefaultImage="img/1.png" PressedImage="2_push.png"/>
This is a much more cleaner and flexible approach.
I am new in wpf user controls, i am trying to create a user control as button which has the half portion .png image and rest have text.
I have designed my user control but unable to set the image and text from windows form. I am invoking my user control in windows form using ElementHost.
Can anyone help me what is wrong i am doing.
My User Control is following.
<UserControl x:Class="MyAssembly.SideNavigationButtons"
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:MyAssembly"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<UserControl.Resources>
<Style x:Key="SideButton" TargetType="{x:Type Button}">
<Setter Property="Background" Value="#70b639"></Setter>
<Setter Property="Foreground" Value="#FF3A700F"></Setter>
<Setter Property="Height" Value="50"></Setter>
<Setter Property="Width" Value="200"></Setter>
<Setter Property="FontFamily" Value="segoe UI"></Setter>
<Setter Property="FontSize" Value="14"></Setter>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border BorderBrush="#FF3A700F" BorderThickness="0,0,0,1">
<Grid Background="{TemplateBinding Background}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image x:Name="imgSideIcon" Grid.Column="0" Margin="5,5,5,5" Source="{DynamicResource ResourceKey=ImageSource}" HorizontalAlignment="Left" VerticalAlignment="Center"/>
<ContentPresenter HorizontalAlignment="Left" Margin="5,5,5,5" VerticalAlignment="Center" Grid.Column="1"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Cursor" Value="Hand" />
<Setter Property="Foreground" Value="White"/>
<Setter Property="Background" Value="#FF3A700F"/>
</Trigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<Grid DataContext="buttonsList">
<Button x:Name="btnSideBar" Click="btnSideBar_Click" Style="{StaticResource SideButton}" Content="Manage Master Data"></Button>
</Grid>
Code behind file for it.
namespace MyAssembly
{
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
public partial class SideNavigationButtons : UserControl
{
public string buttonContent = "";
public string buttonImagePath = "";
public event EventHandler ButtonClick;
ObservableCollection<CustomButtonObject> buttonsList = new ObservableCollection<CustomButtonObject>();
public void SetButtonContent(string imageName, string content)
{
var uri = new Uri("pack://application:,,,/BluePiControls;component/Images/"+ imageName + "");
var bitmap = new BitmapImage(uri);
CustomButtonObject obj = new CustomButtonObject();
obj.ButtonContent = content;
obj.ImageSource = bitmap;
buttonsList.Add(obj);
this.DataContext = buttonsList;
//btnSideBar.Content = content;
}
public SideNavigationButtons()
{
InitializeComponent();
}
private void btnSideBar_Click(object sender, RoutedEventArgs e)
{
if (ButtonClick != null)
ButtonClick(this, e);
}
}
}
Windows form code behind file
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
BluePiControls.SideNavigationButtons sideButtonHD = ( BluePiControls.SideNavigationButtons)elementHost1.Child;
elementHost1.AutoSize = true;
sideButtonHD.SetButtonContent("Image1.png", "First Image");
BluePiControls.SideNavigationButtons sideButtonWH = (BluePiControls.SideNavigationButtons)elementHost2.Child;
elementHost2.AutoSize = true;
sideButtonWH.SetButtonContent("Image2.png", "Second Image");
}
}
I building a UserControl named MenuPopup. And I use this control in project with MVVM.
It like this:
But now, I have no ideal to hide the Popup after click one MenuItem. If hide it by Menuitems' Click events, then how to binding command to ViewModel to handle business logic?
<!--MenuPopup.xaml-->
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:Microsoft_Windows_Themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero"
mc:Ignorable="d"
x:Class="WpfApplication10.MenuPopup"
x:Name="UserControl">
<UserControl.Resources>
<Style x:Key="ButtonFocusVisual">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<Rectangle Margin="2" SnapsToDevicePixels="true" Stroke="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" StrokeThickness="1" StrokeDashArray="1 2"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<LinearGradientBrush x:Key="ButtonNormalBackground" EndPoint="0,1" StartPoint="0,0">
<GradientStop Color="#F3F3F3" Offset="0"/>
<GradientStop Color="#EBEBEB" Offset="0.5"/>
<GradientStop Color="#DDDDDD" Offset="0.5"/>
<GradientStop Color="#CDCDCD" Offset="1"/>
</LinearGradientBrush>
<SolidColorBrush x:Key="ButtonNormalBorder" Color="#FF707070"/>
<Geometry x:Key="ArrowGraph">M 3,6 L 13,6 L 8,12 Z</Geometry>
<Geometry x:Key="LineGraph" >M 12.3,7 L 9,11</Geometry>
<Style x:Key="ArrowMenuButtonStyle" TargetType="{x:Type Button}">
<Setter Property="FocusVisualStyle" Value="{StaticResource ButtonFocusVisual}"/>
<Setter Property="Background" Value="#00FFFFFF"/>
<Setter Property="BorderBrush" Value="#FFFFFFFF"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Padding" Value="1"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Grid>
<Border BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}" BorderThickness="1"/>
<Path x:Name="ArrowPath" Data="{StaticResource ArrowGraph}" Fill="#FFFFFFFF"/>
<Path x:Name="LinePath" Data="{StaticResource LineGraph}" Fill="#FFD5D5D5" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsKeyboardFocused" Value="true"/>
<Trigger Property="ToggleButton.IsChecked" Value="true"/>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="#ADADAD"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Background" Value="#FF219266"/>
<Setter Property="BorderBrush" Value="#FF167559"/>
<Setter Property="Fill" TargetName="LinePath" Value="#FF1E7B57"/>
</Trigger>
<Trigger Property="IsPressed" Value="true">
<Setter Property="Background" Value="#FF219266"/>
<Setter Property="BorderBrush" Value="#FF7ABEA3"/>
<Setter Property="Fill" TargetName="LinePath" Value="#FF1E7B57"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid x:Name="LayoutRoot">
<Grid>
<Button x:Name="MenuButton" Click="MenuButton_Click" Content="" Width="16" Height="16" BorderThickness="0" Padding="0" Style="{DynamicResource ArrowMenuButtonStyle}" />
<Popup x:Name="MenuButtonPopup" StaysOpen="False" PlacementTarget="{Binding ElementName=MenuButton}" >
<Grid>
<Border Background="White">
<StackPanel >
<MenuItem Header="XX1" />
<MenuItem Header="XX2" />
</StackPanel>
</Border>
</Grid>
</Popup>
</Grid>
</Grid>
</UserControl>
.
// MenuPopup.xaml.cs
namespace WpfApplication10
{
/// <summary>
/// Interaction logic for MenuPopup.xaml
/// </summary>
public partial class MenuPopup : UserControl
{
public MenuPopup()
{
this.InitializeComponent();
}
private void MenuButton_Click(object sender, RoutedEventArgs e)
{
MenuButtonPopup.IsOpen = true;
}
}
}
Update:
Now, I use both click event(which button's) and command. The click event handler in code-behind file, and the command in ViewModel.
<!--parts in MenuPopup.xaml -->
<Grid x:Name="LayoutRoot">
<Button x:Name="MenuButton" Click="MenuButton_Click" Content="" Width="16" Height="16" BorderThickness="0" Padding="0" Style="{DynamicResource ArrowMenuButtonStyle}" />
<Popup x:Name="MenuButtonPopup" StaysOpen="False" PlacementTarget="{Binding ElementName=MenuButton}" >
<Grid>
<Border Background="White">
<StackPanel >
<MenuItem Header="XX1" Click="MenuItem_Click" Command="{Binding IncreaseCommand}"/>
<MenuItem Header="XX2" Click="MenuItem_Click" />
</StackPanel>
</Border>
</Grid>
</Popup>
</Grid>
.
// MenuPopup.xaml.cs
namespace WpfApplication10
{
/// <summary>
/// Interaction logic for MenuPopup.xaml
/// </summary>
public partial class MenuPopup : UserControl
{
public MenuPopup()
{
this.InitializeComponent();
}
private void MenuButton_Click(object sender, RoutedEventArgs e)
{
MenuButtonPopup.IsOpen = true;
}
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
MenuButtonPopup.IsOpen = false;
}
}
}
.
// MainViewModel.cs
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
namespace WpfApplication10.ViewModel
{
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
IncreaseCommand = new RelayCommand(() => ++Cnt);
}
private int cnt = 0;
public int Cnt
{
get { return cnt; }
set { cnt = value; RaisePropertyChanged("Cnt"); }
}
private RelayCommand increaseCommand;
public RelayCommand IncreaseCommand { get; private set; }
}
}
You can have bool property on your viewmodel as IsPopupOpen and bind Popup.IsOpen to this property. Now bind your button Command to the command defined in ViewModel and in your commandhandler set IsPopupOpen to false.
Thanks