Pass custom properties in WPF templates - c#

I'm trying to create a menu that works with radio buttons. The buttons are graphically prettied by a template. here I would like to display an icon and a text. However, I don't know how I can pass several parameters, so far I only pass the text and have not yet found a way to pass the image.
<StackPanel Grid.Row="1" Margin="0,10,0,0">
<RadioButton Content="Dashboard"
IsChecked="True"
Style="{StaticResource MenuButtonTheme}"/>
<RadioButton Content="Product"
Style="{StaticResource MenuButtonTheme}"/>
<RadioButton Content="Inventory"
Style="{StaticResource MenuButtonTheme}"/>
</StackPanel>
Style Of the Radiobutton
<Style BasedOn="{StaticResource {x:Type ToggleButton}}"
TargetType="{x:Type RadioButton}"
x:Key="MenuButtonTheme">
<Style.Setters>
<Setter Property="Foreground" Value="#FFFFFF"/>
<Setter Property="FontFamily" Value="/Fonts/#Poppins"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="RadioButton">
<Border Background="{TemplateBinding Background}"
CornerRadius="5"
Margin="5,0,5,0">
<Grid VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
Height="50"
>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<iconPacks:PackIconBoxIcons Kind="SolidPieChartAlt2"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Margin="10,0,0,0"/>
<TextBlock Grid.Column="1" Text="{TemplateBinding Property=Content}"
VerticalAlignment="Center"
FontSize="20"
FontWeight="Regular"
Margin="10,0,0,0"/>
</Grid>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
</Style.Setters>
<Style.Triggers>
<Trigger Property="IsChecked" Value="true">
<Setter Property="Background" Value="#212121"/>
<Setter Property="Foreground" Value="#4169E1"/>
</Trigger>
</Style.Triggers>
</Style>

Foreach particular part of UI in your application, I recommend you to make it a module, that is, a UserContol or ContentControl(recommened). These controls corresponds to View in MVVM, and foreach of them you should add a View Model.
namespace MyNameSpace{
public class View<T> : ContentControl {
public T ViewModel {
get { return (T)GetValue(ViewModelProperty); }
set { SetValue(ViewModelProperty, value); }
}
public static readonly DependencyProperty ViewModelProperty =
DependencyProperty.Register("ViewModel", typeof(T), typeof(View<T>), new PropertyMetadata());
}
public abstract class ViewModel : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged([CallerMemberName] String propertyName = null) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
If the relative logic is purely UI, then Model is not needed in this case.
Your View's xaml should look like this:
<local:View x:TypeArguments="MyAViewModel" x:Name="view"
x:Class="MyNameSpace.MyAView"
skip
xmlns:local="clr-namespace:MyNameSpace">
<Image Source="{Binding ViewModel.ImageSource,ElementName=view}"/>
</local:View>
Your ViewModel should look like this:
public class MyAViewModel: ViewModel {
public AbilityViewModel() {//Constructor with parameter
//Set the image source here
}
private ImageSource imageSource;
public ImageSource ImageSource{
get => imageSource
set{
imageSource = value;
NotifyPropertyChanged();
}
}
}
In the root element of your UI hierarchy, for example your MainWindow, add your custom contols:
<Window x:Name="window" skip>
<Grid>
<local:MyAView ViewModel="{Binding MyAViewModel,ElementName=window}"/>
<local:MyBView ViewModel="{Binding MyBViewModel,ElementName=window}"/>
</Grid>
</Window>
You may either do so with adding dependency properies of the MyAViewModel and MyBViewModel to your MainWindow, or just set MyAView's ViewModel in MainWindow's constructor or loaded event. You may create the ViewModel to pass to view, in which ImageSource is initialized in constructor, or change it after its construction by somewhere in your code.
Above codes are just demo, directly written in stackoverflow's webpage and is not tested. You may ask me if there is any problem.

Related

Problem with ActivateItem by the ContextMenu on DataGrid using CaliburnMicro

I am using caliburn micro on my project. So far I have had no problems with ActivateItemAsync. Now, however, this method does not activate my ActiveItem. Now what my code looks like:
I have a DashboardMainView:
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Ribbon ...>
</Ribbon>
<Grid Grid.Row="1" Background="#E8E8E8">
<ContentControl x:Name="ActiveItem" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
</ContentControl>
</Grid>
Then in DashboardMainViewModel:
public class DashboardMainViewModel : Conductor<object>
{
private IDialogCoordinator dialogCoordinator;
public DashboardMainViewModel(IDialogCoordinator instance)
{
this.dialogCoordinator = instance;
ActivateItemAsync(new DashboardSummaryViewModel());
}
public async Task Execution()
{
await ActivateItemAsync(new BasicExecutionViewModel(DialogCoordinator.Instance));
}
So far everything works, but then BasicExecutionView is activated, where I have a DataGrid and MenuContext in it. And here comes the problem, when we right-click on the DataGrid, a menu pops up where, after selecting an interesting option, it should activate another view ... but it does not. My code looks like this:
<Grid >
<Viewbox Stretch="Fill">
<DataGrid
x:Name="BasicExecutionGrid"
CanUserSortColumns="True"
IsReadOnly="True"
CanUserAddRows="False"
FontSize="11"
Height ="800"
ScrollViewer.CanContentScroll="True"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Auto" AutoGeneratingColumn="BasicExecutionGrid_AutoGeneratingColumn">
<DataGrid.ColumnHeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock TextWrapping="Wrap"
Text="{Binding}"
HorizontalAlignment="Center"
VerticalAlignment="Center"></TextBlock>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="Background" Value="LightSteelBlue"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Height" Value="45"/>
</Style>
</DataGrid.ColumnHeaderStyle>
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Header="Details"
Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}, Path=DataContext.ActivateCellDetailsView}"
>
</MenuItem>
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
</Viewbox>
</Grid>
In BasicExecutionViewModel I have:
class BasicExecutionViewModel : Conductor<object>
{
private IDialogCoordinator dialogCoordinator;
private RelayCommand activateCellDetailsView { get; set; }
public BasicExecutionViewModel(IDialogCoordinator instance)
{
this.dialogCoordinator = instance;
}
public ICommand ActivateCellDetailsView
{
get
{
if (activateCellDetailsView == null)
{
activateCellDetailsView = new RelayCommand(async p=> await ActivateCellView());
}
return activateCellDetailsView;
}
}
public async Task ActivateCellView()
{
await ActivateItemAsync(new CellDetailsExecutionViewModel());
}
}
The code comes to ActivateCellView () and activates it. I can see it in Output Window where ActiveItem takes the value of the CellDetailsExecutionViewModel () object, but BasicExecutionView is still displayed on the screen. What am I doing wrong? I guess it's something with either DataContext or parent-child issue ... please help :)
PS. I'm not a professional programmer I'm a hobbyist.
Solved
I solved the problem. My mistake was using Conductor<object> incorrectly. In DashboardMainViewModel. When I corrected on
DashboardMainViewModel: Conductor<object>.Collection.OneActive
ant the same in the BasicExecutionViewModel
BasicExecutionViewModel: Conductor<object>.Collection.OneActive
I also updated the code in the ActivateCellView() method to
public async Task ActivateCellView()
{
CellDetailsExecutionViewModel cellDetailsExecutionViewAcvtivate = new CellDetailsExecutionViewModel();
var parentConductor = (Conductor<object>.Collection.OneActive)(this.Parent);
await parentConductor.ActivateItemAsync(cellDetailsExecutionViewAcvtivate);
}
And everything works beautifully

Why do my triggers end up with a blank control?

In WPF I'm trying to create a "flag" control that displays a checkmark or an X based on a bound dependency property (Flag)
<UserControl x:Name="Root" (Other user control stuff)>
<ContentControl Height="20" x:Name="flagHolder">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=Root, Path=Flag}" Value="False">
<Setter Property="Content" Value="{StaticResource XIcon}" />
<Setter Property="Foreground" Value="Crimson"/>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=Root, Path=Flag}" Value="True">
<Setter Property="Content" Value="{StaticResource CheckIcon}" />
<Setter Property="Foreground" Value="ForestGreen"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</UserControl>
On startup every icon is correct (I have several of these controls, each bound to different values). However, when I toggle a few (one that was "off" turns "on" and the one currently "on" turns "off") I see two things:
The control that was turned "on" has become a green check (as desired)
The control that was turned "off" is now just blank
Inspecting the visual tree seems to indicate that everything is working (though I could easily be missing something here), and the order of the triggers doesn't seem to matter. What am I doing wrong?
Here is an example icon, the path geometry is removed since its just noise:
<Viewbox x:Key="CheckIcon" x:Shared="False">
<Path Style="{StaticResource IconPathStyle}">
<Path.Data>
<PathGeometry Figures="Bunch of SVG" FillRule="NonZero"/>
</Path.Data>
</Path>
</Viewbox>
I'm unable to reproduce your issue, but here what I have and it's working:
App.xaml
<Application x:Class="WpfApplication1.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
<Viewbox x:Key="CheckIcon" x:Shared="False">
<Canvas Height="24" Width="32">
<Path Width="7.85446" Height="8.57578" Canvas.Left="-0.0522281" Canvas.Top="-0.100391" Stretch="Fill" StrokeThickness="1.04192" StrokeMiterLimit="2.75" Stroke="#FF000000" Data="F1 M 0.468732,4.66838L 3.03345,7.95443L 7.28127,0.420569"/>
</Canvas>
</Viewbox>
<Viewbox x:Key="XIcon" x:Shared="False">
<Canvas Height="24" Width="32">
<Path Data="M0,0 L1,1 M0,1 L1,0" Stretch="Fill" Stroke="Black" StrokeThickness="3" Width="12" Height="12" />
</Canvas>
</Viewbox>
</Application.Resources>
</Application>
YesNo.xaml
<UserControl x:Class="WpfApplication1.YesNo" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Name="Root">
<ContentControl Height="20" Name="flagHolder">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=Root, Path=Flag}" Value="False">
<Setter Property="Content" Value="{StaticResource XIcon}" />
<Setter Property="Foreground" Value="Crimson"/>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=Root, Path=Flag}" Value="True">
<Setter Property="Content" Value="{StaticResource CheckIcon}" />
<Setter Property="Foreground" Value="ForestGreen"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</UserControl>
YesNo.xaml.cs
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
public partial class YesNo : UserControl
{
public YesNo()
{
InitializeComponent();
}
public static readonly DependencyProperty FlagProperty = DependencyProperty.Register(
"Flag", typeof(bool), typeof(YesNo), new PropertyMetadata(default(bool)));
public bool Flag {
get {
return (bool) GetValue(FlagProperty);
}
set {
SetValue(FlagProperty, value);
}
}
}
}
MainWindow.xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpfApplication1="clr-namespace:WpfApplication1"
xmlns:system="clr-namespace:System;assembly=mscorlib"
Title="" Width="400" Height="400">
<StackPanel Orientation="Vertical" Margin="50">
<wpfApplication1:YesNo Flag="{Binding Flag1}"/>
<wpfApplication1:YesNo Flag="{Binding Flag2}"/>
<wpfApplication1:YesNo Flag="{Binding Flag2}"/>
<wpfApplication1:YesNo Flag="{Binding Flag1}"/>
<Button Content="Toggle" Click="ButtonBase_OnClick"></Button>
</StackPanel>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : INotifyPropertyChanged
{
private bool _flag1;
private bool _flag2;
public MainWindow()
{
InitializeComponent();
DataContext = this;
Flag1 = true;
Flag2 = false;
}
public bool Flag1 {
get {
return _flag1;
}
set {
_flag1 = value;
OnPropertyChanged();
}
}
public bool Flag2 {
get {
return _flag2;
}
set {
_flag2 = value;
OnPropertyChanged();
}
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e) {
Flag1 = !Flag1;
Flag2 = !Flag2;
}
}
How it looks like:
Video: http://www.screencast.com/t/J5IY7DR3Ry

Switch out UserControl based on property

How do I switch UserControls based on a property setting in my ViewModel?
If Vm.View = "A"
<Window>
<local:UserControlA/>
</Window>
If Vm.View = "B"
<Window>
<local:UserControlB/>
</Window>
Vm.View is an enum that someday may allow for C, D, and so on. Both UserControls are bound to the same Vm, but they present the data radically different based on the user's input. So a DataTemplate based on type doesn't really work here.
Thoughts?
Add ContentControl inside Window and based on View value you can set it's ContentTemplate using DataTriggers.
<ContentControl Content="{Binding}">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<local:UserControlA/>
</DataTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding View}" Value="B">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<local:UserControlB/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
You might leverage DataTemplate's DataType property and let the binding engine take care of the rest...
XAML
<Window.Resources>
<DataTemplate DataType="localEnums:ProduceType.Apples">
<local:ApplesView />
</DataTemplate>
<DataTemplate DataType="localEnums:ProduceType.Oranges">
<local:OrangesView />
</DataTemplate>
</Window.Resources>
<StackPanel>
<ContentPresenter Content="{Binding ProduceType}" />
<Button Content="Change Produce" Click="Button_Click"/>
</StackPanel>
View Model
public class ProduceViewModel : ViewModel
{
public ProduceViewModel()
{
this.ProduceType = ProduceType.Apples;
}
private ProduceType _produceType;
public ProduceType ProduceType
{
get
{
return _produceType;
}
set
{
if (_produceType != value)
{
_produceType = value;
RaisePropertyChanged();
}
}
}
}
Button Click Handler (Violates pure MVVM but just to demonstrate the DataTemplate switching)
private void Button_Click(object sender, RoutedEventArgs e)
{
(this.DataContext as ProduceViewModel).ProduceType = ProduceType.Oranges;
}

Bind DependencyProperty in style

I'm sure this is a simple thing to do, and to look up, but I've yet to have any luck. Basically I want to style a WPF Slider such that when used I can give it two additional strings to display.
The new control looks like this:
public class SliderPicker : Slider
{
public string LeftLabel
{
get { return (string)GetValue(LeftLabelProperty); }
set { SetValue(LeftLabelProperty, value); }
}
public static readonly DependencyProperty LeftLabelProperty =
DependencyProperty.Register("LeftLabel", typeof(string), typeof(SliderPicker), new UIPropertyMetadata(String.Empty));
public string RightLabel
{
get { return (string)GetValue(RightLabelProperty); }
set { SetValue(RightLabelProperty, value); }
}
public static readonly DependencyProperty RightLabelProperty =
DependencyProperty.Register("RightLabel", typeof(string), typeof(SliderPicker), new UIPropertyMetadata(String.Empty));
}
The style like this:
<Style x:Key="SlickSlider" TargetType="{x:Type Slider}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Slider}">
...
<TextBlock Text="{TemplateBinding LeftLabel}" Grid.Row="2" HorizontalAlignment="Left" />
<TextBlock Text="{TemplateBinding RightLabel}" Grid.Row="2" HorizontalAlignment="Right" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And usage:
<controls:SliderPicker LeftLabel="RealPreference" RightLabel="RealCoverage" Width="400" Style="{StaticResource SlickSlider}"/>
This doesn't seem to work. So, how do I set the DP on the control and show it in the template? I thought that's what TemplateBinding was for?
You only need a small fix. Change {x:Type Slider} to {x:Type controls:SliderPicker}
You need to apply the custom control as the type in your style and template. Without it your trying to look for LeftLabelProperty and RightLabelProperty in Slider and not SliderPicker with your template binding.
Change your style to
<Style x:Key="SlickSlider" TargetType="{x:Type controls:SliderPicker}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:SliderPicker}">
...
<TextBlock Text="{TemplateBinding LeftLabel}" Grid.Row="2" HorizontalAlignment="Left" />
<TextBlock Text="{TemplateBinding RightLabel}" Grid.Row="2" HorizontalAlignment="Right" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Have tried this with the class you've posted and it works fine :)

How can I create elements with trigger invoke method in SilverLight?

This question is a continuation of the pregoing one.( How can I combine some UserControls in SilverLight?)
I have 3 view models with different colour properties.
How can I create elements of User Control with trigger invoke method after pressing the button on the element.
Here is a code of this element that I have upgrade with the trigger action.
<UserControl x:Class="SilverlightApplication14.NodePicture"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:SilverlightApplication14"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity">
<Grid x:Name="LayoutRootNodePicture" Height="100" Width="100"
HorizontalAlignment="Center">
<Canvas x:Name="ParentCanvas" Background="{Binding NodeColor}" Width="100" Height="100" >
</Canvas>
<Image HorizontalAlignment="Center"
Source="add.png"
Stretch="Fill"
Width="16"
VerticalAlignment="Top"
Margin="0,0,2,2"
Height="16" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<local:Add />
</i:EventTrigger>
</i:Interaction.Triggers>
</Image>
</Grid>
</UserControl>
And the code with the trigger action
namespace SilverlightApplication14
{
public class Add : TriggerAction<FrameworkElement>
{
protected override void Invoke(object parameter)
{
var vm = AssociatedObject.DataContext as NodeViewModel;
if (vm != null)
{
if (vm.Nodes == null)
{
vm.Nodes = new ObservableCollection<NodeViewModel>();
}
var child = new NodeViewModel { NodeColor = new SolidColorBrush(Color.FromArgb(255, 255, 0, 0)) };
vm.Nodes.Add(child);
}
}
}
}
Updated code:
<Grid>
<Grid.Resources>
<Style x:Key="myStyle" TargetType="ListBoxItem">
<Setter Property="Background" Value="Khaki" />
<Setter Property="Foreground" Value="DarkSlateGray" />
<Setter Property="Margin" Value="5" />
<Setter Property="FontStyle" Value="Italic" />
<Setter Property="FontSize" Value="14" />
<Setter Property="BorderBrush" Value="DarkGray" />
</Style>
</Grid.Resources>
<ListBox ItemsSource="{Binding Nodes}" ItemContainerStyle="{StaticResource myStyle}">
<ListBox.ItemTemplate>
<DataTemplate>
<local:NodePicture DataContext="{Binding}" />
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</Grid>
Is there a simple (or a right way ) way of doing this?
It is preferable to work with business-logic in view models, whereas triggers are intended for working with UI.
I would change the trigger to a command:
<Button Command="{Binding AddCommand}">
<Button.Template>
<ControlTemplate TargetType="Button">
<Image ... />
</ControlTemplate>
</Button.Template>
</Button>
When a user clicks the button, the AddCommand is invoked. It can be implemented in the view model so:
public class NodeViewModel
{
public NodeViewModel()
{
this.AddCommand = new RelayCommand(obj => { /* do something */ });
}
public RelayCommand AddCommand { get; private set; }
//...
}
The RelayCommand class is one of the possible implementations and it can be downloaded with the MVVM Light framework here.

Categories

Resources