I am finding that I am creating the same Button style for multiple buttons but only changing one part - the image that is used on the Button. An example;
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Source="pack://application:,,,/Resources/MainWindowIcons/Staff.ico" Height="20"/>
<TextBlock Style="{StaticResource HoverUnderlineStyle}" Text="Staff" Margin="5,0,0,0"/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
This is the code for the staff Button. If I wanted to add another button I'd replicate the whole style but just change the Source of the Image.
Is there a way I can have on style and then set this on the Button itself - meaning that I don't have to replicate the style multiple times?
You could implement two attached properties - one for the Image source and one for the text - that you can set on any Button:
public class ButtonProperties
{
public static readonly DependencyProperty ImageSourceProperty =
DependencyProperty.RegisterAttached("ImageSource", typeof(Uri), typeof(ButtonProperties));
public static Uri GetImageSource(Button button)
{
return (Uri)button.GetValue(ImageSourceProperty);
}
public static void SetImageSource(Button button, Uri value)
{
button.SetValue(ImageSourceProperty, value);
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.RegisterAttached("Text", typeof(Uri), typeof(ButtonProperties));
public static string GetText(Button button)
{
return (string)button.GetValue(ImageSourceProperty);
}
public static void SetText(Button button, string value)
{
button.SetValue(ImageSourceProperty, value);
}
}
Then you only need to define the ContentTemplate once as a resource, for example in your 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"
xmlns:local="clr-namespace:WpfApplication1"
StartupUri="MainWindow.xaml">
<Application.Resources>
<DataTemplate x:Key="dataTemplate">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Path=(local:ButtonProperties.ImageSource), RelativeSource={RelativeSource AncestorType=Button}}" Height="20"/>
<TextBlock Text="{Binding Path=(local:ButtonProperties.Text), RelativeSource={RelativeSource AncestorType=Button}}" Margin="5,0,0,0"/>
</StackPanel>
</DataTemplate>
</Application.Resources>
</Application>
Usage:
<Button local:ButtonProperties.Text="Staff"
local:ButtonProperties.ImageSource="pack://application:,,,/Resources/MainWindowIcons/Staff.ico"
ContentTemplate="{StaticResource dataTemplate}" />
Related
Background
I am making a custom control that has multiple ListBox's. I want to make this control MVVM compliant, so I am keeping any XAML and the code behind agnostic with respect to any ViewModel. One ListBox is simply going to be a list of TextBox's while the other is going to have a canvas as the host to display the data graphically. Both of these ListBox's are children of this custom control.
Pseudo example for the custom control template:
<CustomControl>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox1 Grid.Column="0"/>
<ListBox2 Grid.Column="1"/>
</CustomControl>
The code behind for this custrom control would have a dependency property that will serve as the ItemsSource, fairly standard stuff:
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(UserControl1), new PropertyMetadata(new PropertyChangedCallback(OnItemsSourcePropertyChanged)));
private static void OnItemsSourcePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var control = sender as UserControl1;
if (control != null)
control.OnItemsSourceChanged((IEnumerable)e.OldValue, (IEnumerable)e.NewValue);
}
Where I am stuck
Because the two ListBox's are using the same data source but just display the data differently, I want the ItemsSource defined as one of the the parent view's dependency properties to be the ItemsSource for the two children. From the ViewModel side, this items source can be some sort of ObservableCollection<ChildViewModels>, or IEnumerable, or whatever it wants to be.
How can I point to properties from the ItemsSource's ViewModel to dependency properties of the child views?
I was hoping to get something similar to how it could be done outside of a custom view:
Example Parent ViewModel(omitting a lot, assume all functioning):
public class ParentViewModel
{
public ObservableCollection<ChildViewModel> ChildViewModels;
}
Example ViewModel (omitting INotifyPropertyChanged and associated logic):
public class ChildViewModel
{
public string Name {get; set;}
public string ID {get; set;}
public string Description {get; set;}
}
Example control (ommitting setting the DataContext, assume set properly):
<ListBox ItemsSource="{Binding ChildViewModels}">
<ListBox.ItemsTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text ="{Binding Description}"/>
</StackPanel>
</ListBox.ItemsTemplate>
</ListBox>
How can I do something similar where I can pass the properties from the ItemsSource to the child views on a custom control?
Many thanks
If I understand correctly what you need, then here is an example.
Add properties for element templates in both lists and style for Canvas.
using System.Collections;
using System.Windows;
using System.Windows.Controls;
namespace Core2022.SO.jgrmn
{
public class TwoListControl : Control
{
static TwoListControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(TwoListControl), new FrameworkPropertyMetadata(typeof(TwoListControl)));
}
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register(
nameof(ItemsSource),
typeof(IEnumerable),
typeof(TwoListControl),
new PropertyMetadata((d, e) => ((TwoListControl)d).OnItemsSourceChanged((IEnumerable)e.OldValue, (IEnumerable)e.NewValue)));
private void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
//throw new NotImplementedException();
}
public DataTemplate TemplateForStack
{
get { return (DataTemplate)GetValue(TemplateForStackProperty); }
set { SetValue(TemplateForStackProperty, value); }
}
public static readonly DependencyProperty TemplateForStackProperty =
DependencyProperty.Register(
nameof(TemplateForStack),
typeof(DataTemplate),
typeof(TwoListControl),
new PropertyMetadata(null));
public DataTemplate TemplateForCanvas
{
get { return (DataTemplate)GetValue(TemplateForCanvasProperty); }
set { SetValue(TemplateForCanvasProperty, value); }
}
public static readonly DependencyProperty TemplateForCanvasProperty =
DependencyProperty.Register(
nameof(TemplateForCanvas),
typeof(DataTemplate),
typeof(TwoListControl),
new PropertyMetadata(null));
public Style StyleForCanvas
{
get { return (Style)GetValue(StyleForCanvasProperty); }
set { SetValue(StyleForCanvasProperty, value); }
}
public static readonly DependencyProperty StyleForCanvasProperty =
DependencyProperty.Register(
nameof(StyleForCanvas),
typeof(Style),
typeof(TwoListControl),
new PropertyMetadata(null));
}
}
In the theme (Themes/Generic.xaml), set bindings to these properties:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:jgrmn="clr-namespace:Core2022.SO.jgrmn">
<Style TargetType="{x:Type jgrmn:TwoListControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type jgrmn:TwoListControl}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox Grid.Column="0"
ItemsSource="{TemplateBinding ItemsSource}"
ItemTemplate="{TemplateBinding TemplateForStack}"/>
<ListBox Grid.Column="1"
ItemsSource="{TemplateBinding ItemsSource}"
ItemTemplate="{TemplateBinding TemplateForCanvas}"
ItemContainerStyle="{TemplateBinding StyleForCanvas}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ListBox>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Window with an example of use:
<Window x:Class="Core2022.SO.jgrmn.TwoListWindow"
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:local="clr-namespace:Core2022.SO.jgrmn"
mc:Ignorable="d"
Title="TwoListWindow" Height="250" Width="400">
<FrameworkElement.DataContext>
<CompositeCollection>
<Point>15 50</Point>
<Point>50 150</Point>
<Point>150 50</Point>
<Point>150 150</Point>
</CompositeCollection>
</FrameworkElement.DataContext>
<Grid>
<local:TwoListControl ItemsSource="{Binding}">
<local:TwoListControl.TemplateForStack>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}Point ({0} {1})">
<Binding Path="X"/>
<Binding Path="Y"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</local:TwoListControl.TemplateForStack>
<local:TwoListControl.TemplateForCanvas>
<DataTemplate>
<Ellipse Width="10" Height="10" Fill="Red"/>
</DataTemplate>
</local:TwoListControl.TemplateForCanvas>
<local:TwoListControl.StyleForCanvas>
<Style TargetType="ListBoxItem">
<Setter Property="Canvas.Left" Value="{Binding X}"/>
<Setter Property="Canvas.Top" Value="{Binding Y}"/>
</Style>
</local:TwoListControl.StyleForCanvas>
</local:TwoListControl>
</Grid>
</Window>
You must spend all participating controls a ItemsSource property. The idea is to delegate the source collection from the parent to the child controls and finally to the ListBox. The ItemsSource properties should be a dependency property of type IList and not IEnumerable. This way you force the binding source to be of type IList which improves the binding performance.
To allow customization of the actual displayed items, you must either
a) spend every control a ItemTemplate property of type DataTemplate and delegate it to the inner most ListBox.ItemTemplate (similar to the ItemsSource property) or
b) define the template as a resource (implicit template, which is a key less DataTemplate).
The example implements a):
<Window>
<Window.DataContext>
<ParentViewModel />
</Window.DataCOntext>
<CustomControl ItemsSource="{Binding ChildViewModels}">
<CustomControl.ItemsTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text ="{Binding Description}"/>
</StackPanel>
</CustomControl.ItemsTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox1 Grid.Column="0"
ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=ItemsSource}"
ItemTemplate="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=ItemTemplate}" />
<ListBox2 Grid.Column="1"
ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=ItemsSource}"
ItemTemplate="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=ItemTemplate}" />
</CustomControl>
</Window>
Inside the child controls (ListBox1 and ListBox2):
<UserControl>
<ListBox ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=ItemsSource}"
ItemTemplate="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=ItemTemplate}" />
</UserControl>
I'm working on a modal dialog popup (I'm not sure about the exact UX term) that is displayed inline, inside of a control or window with darkened background.
Visual example
What I tried is putting a <ContentPresenter /> inside the XAML of the popup and then just instantiate it like this:
<local:Popup Grid.RowSpan="2">
<TextBlock Text="Popup test..." />
</local:Popup>
However, the XAML replaces the entire Popup XAML instead of being placed where the ContentPresenter is.
Q: How is the ContentPresenter here used properly?
Popup.xaml
<ContentControl
x:Class="[...].Popup"
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:[...]"
mc:Ignorable="d" d:DesignWidth="300" d:DesignHeight="300">
<Grid Background="#7f000000">
<Grid Background="White" HorizontalAlignment="Center" VerticalAlignment="Center">
<StackPanel Margin="20">
<TextBlock Text="{Binding Title, RelativeSource={RelativeSource AncestorType=UserControl}}" FontSize="20" />
<ContentPresenter />
</StackPanel>
</Grid>
</Grid>
</ContentControl>
Popup.xaml.cs
using System.Windows;
namespace [...]
{
public partial class Popup : ContentControlBase
{
public static DependencyProperty TitleProperty = DependencyProperty.Register(nameof(Title), typeof(string), typeof(Popup));
public string Title
{
get
{
return (string)GetValue(TitleProperty);
}
set
{
SetValue(TitleProperty, value);
}
}
public Popup()
{
InitializeComponent();
}
}
}
The content of your Popup should be defined as a ControlTemplate for the ContentPresenter to work as expected here. Please refer to the following sample code.
Popup.xaml:
<ContentControl
x:Class="WpfApplication1.Popup"
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:WpfApplication1"
mc:Ignorable="d" d:DesignWidth="300" d:DesignHeight="300"
x:Name="popup">
<ContentControl.Template>
<ControlTemplate TargetType="local:Popup">
<Grid Background="#7f000000">
<Grid Background="White" HorizontalAlignment="Center" VerticalAlignment="Center">
<StackPanel Margin="20">
<TextBlock Text="{Binding Title, ElementName=popup}" FontSize="20" />
<ContentPresenter />
</StackPanel>
</Grid>
</Grid>
</ControlTemplate>
</ContentControl.Template>
Popup1.xaml.cs.
public partial class Popup : ContentControl
{
public static DependencyProperty TitleProperty = DependencyProperty.Register(nameof(Title), typeof(string), typeof(Popup));
public string Title
{
get
{
return (string)GetValue(TitleProperty);
}
set
{
SetValue(TitleProperty, value);
}
}
public Popup()
{
InitializeComponent();
}
}
}
Window1.xaml:
<local:Popup Title="Title...">
<TextBlock>Text...</TextBlock>
</local:Popup>
I have a custom control in my WPF project, I've defined the style, the class, and I've called it in my xaml and made sure to include a reference. when I start the project 50% of the time the custom object renders perfectly, the other 50% it just doesn't show up, but is still interactable.
public class PermissionBox : Control
{
public PermissionBox()
{
DefaultStyleKey = typeof(PermissionBox);
}
public string PumpID
{
get
{
return (string)GetValue(PumpIDProperty);
}
set
{
SetValue(PumpIDProperty, value);
}
}
public static readonly DependencyProperty PumpIDProperty =
DependencyProperty.Register("PumpID", typeof(string), typeof(PermissionBox), new PropertyMetadata(string.Empty));
public string FuelType
{
get
{
return (string)GetValue(FuelTypeProperty);
}
set
{
SetValue(FuelTypeProperty, value);
}
}
public static readonly DependencyProperty FuelTypeProperty =
DependencyProperty.Register("FuelType", typeof(string), typeof(PermissionBox), new PropertyMetadata(string.Empty));
}
in Themes/Generic.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:POS">
<Style TargetType="{x:Type controls:PermissionBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:PermissionBox}">
<Grid Background="Gray" Opacity="0.8">
<TextBlock Foreground="Black" FontSize="15" Text="Pump ID:" HorizontalAlignment="Left"/>
<TextBlock Foreground="Black" FontSize="15" Text="{TemplateBinding PumpID}" HorizontalAlignment="Right"/>
<TextBlock Foreground="Black" FontSize="15" Text="Fuel Type:" HorizontalAlignment="Left"/>
<TextBlock Foreground="Black" FontSize="15" Text="{TemplateBinding FuelType}" HorizontalAlignment="Right"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
and the calling window
<Window x:Class="POS.Window1"
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:controls="clr-namespace:POS"
mc:Ignorable="d"
Title="Window1" Height="300" Width="300">
<Grid>
<controls:PermissionBox PumpID="100" FuelType="Unleaded"> </controls:PermissionBox>
</Grid>
</Window>
Really appreciate any help with resolving this.
What I have:
User class
public class MyButton
{
public String ButtonProperty { get; set; }
public String LabelProperty { get; set; }
public MyButton()
{
ButtonProperty = "MyButtonText!";
LabelProperty = "LabelText!";
}
}
DataTemplate defined in window resources
<Window.Resources>
<DataTemplate DataType="{x:Type local:MyButton}">
<Border Width="100" Height="100" BorderThickness="2" BorderBrush="Aquamarine">
<StackPanel >
<Button>
<TextBlock Text="{Binding ButtonProperty}"></TextBlock>
</Button>
<Label Content="{Binding LabelProperty}"></Label>
</StackPanel>
</Border>
</DataTemplate>
</Window.Resources>
I want to DataTemplate will draw instead of instance of MyButton class
<Window x:Class="WpfApplication7.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication7"
Title="MainWindow" Height="500" Width="800">
<Window.Resources>
<DataTemplate DataType="{x:Type local:MyButton}">
<Border Width="100" Height="100" BorderThickness="2" BorderBrush="Aquamarine">
<StackPanel >
<Button>
<TextBlock Text="{Binding ButtonProperty}">
</TextBlock>
</Button>
<Label Content="{Binding LabelProperty}">
</Label>
</StackPanel>
</Border>
</DataTemplate>
</Window.Resources>
<!-- Create instance of MyButton in XAML-->
<local:MyButton></local:MyButton>
</Window>
It works fine, but it is not what I want at the end. What if instance of MyButton will DataContext for Window?
public MainWindow()
{
//Set instance of MyButton as DataContext
DataContext = new MyButton();
InitializeComponent();
}
I thought I must write that in XAML-side
<ContentControl DataContext="{Binding}">
<!--MyButton XAML code from DataTemplate here -->
</ContentControl>
instead of
<local:MyButton></local:MyButton>
but it doesn't work at all. what I am doing wrong?
You should try to bind to the Content property of your ContentControl instead of the DataContext property :
<ContentControl Content={Binding } />
Besides, the DataContext of the ContentControl is already the MyButton.
I'm not really sure what are you trying to achieve there. IF you simply want to extend the functionality of the default Button you could define attached properties
Why would you want the DataContext of your Window to be the Button? Maybe the other way around? Not sure I understood that part correctly.
I'm trying to create a UserControl which is a legend of a graph item,
I've defined it so it has a label with the graph name, a check box which
define whether we show it or not and a rectangle with the graph color.
The xaml is defined like this:
<UserControl x:Class="Resources.LegendItem"
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"
mc:Ignorable="d">
<UserControl.Template>
<ControlTemplate>
<StackPanel Margin="1,0,0,0" Orientation="Horizontal">
<CheckBox Name="cb" IsChecked="{TemplateBinding IsGraphVisible}" >
<StackPanel Margin="1,0,0,0" Orientation="Horizontal">
<Rectangle Name="rec" RadiusX="2" RadiusY="2" Height="10" Width="10" />
<Label Name="lab" Content="{TemplateBinding GraphName}" />
</StackPanel>
</CheckBox>
</StackPanel>
</ControlTemplate>
</UserControl.Template>
</UserControl>
and the cs file is:
namespace Resources
{
public partial class LegendItem : UserControl
{
public static readonly DependencyProperty IsGraphVisibleProperty = DependencyProperty.Register("IsGraphVisible", typeof(Boolean), typeof(LegendItem));
public static readonly DependencyProperty GraphNameProperty = DependencyProperty.Register("GraphName", typeof(String), typeof(LegendItem));
public bool IsGraphVisible
{
get { return (bool)GetValue(IsGraphVisibleProperty); }
set { SetValue(IsGraphVisibleProperty, value); }
}
public string GraphName
{
get { return (string)GetValue(GraphNameProperty); }
set { SetValue(GraphNameProperty, value); }
}
public LegendItem()
{
InitializeComponent();
}
}
}
But when I compile it, I get an error "Cannot find the static member 'IsGraphVisibleProperty' on the type 'Control'."
Any help would be appreciated.
You do not need a template at all. UserControl allows the XAML to be declared directly. You can't set the template in a UserControl:
<UserControl x:Class="Resources.LegendItem" x:Name="MyControl"
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"
mc:Ignorable="d">
<StackPanel Margin="1,0,0,0" Orientation="Horizontal">
<CheckBox Name="cb" IsChecked="{Binding ElementName=MyControl, Path=IsGraphVisible}" >
<StackPanel Margin="1,0,0,0" Orientation="Horizontal">
<Rectangle Name="rec" RadiusX="2" RadiusY="2" Height="10" Width="10" />
<Label Name="lab" Content="{Binding ElementName=MyControl, Path=GraphName}" />
</StackPanel>
</CheckBox>
</StackPanel>
</UserControl>
You need to specify TargetType="{x:Type Resources.LegendItem}" on your ControlTemplate, otherwise it defaults to being a template for Control and you get that error.