I have Attached Property and ControlTemplate using the attached property.
Attached Propery:
using System.Windows;
namespace NoteProjectV2.classes.frmtopicclasses
{
class togglebuttonimage : DependencyObject
{
public static readonly DependencyProperty togglebuttonimagesource = DependencyProperty.RegisterAttached("ImageSource", typeof(string), typeof(togglebuttonimage), new PropertyMetadata(default(string)));
public static void Settogglebuttonimagesource(UIElement element, string value)
{
element.SetValue(togglebuttonimagesource, value);
}
public static string Gettogglebuttonimagesource(UIElement element)
{
return (string)element.GetValue(togglebuttonimagesource);
}
}
}
This is my control template (I used this in togglebutton)
<Application x:Class="NoteProjectV2.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:NoteProjectV2"
xmlns:m="clr-namespace:NoteProjectV2.classes.frmtopicclasses"
StartupUri="frmTopic.xaml">
<Application.Resources>
<Style x:Key="togglebutton_topic_menu_normal" TargetType="ToggleButton">
<Setter Property="Width" Value="40" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Border Name="border">
<Border.Style>
<Style>
<Setter Property="Border.Background" Value="Black"/>
</Style>
</Border.Style>
<Image Width="22" Height="22" Name="image" >
<Image.Style>
<Style>
Only This is not working===============> <Setter Property="Image.Source" Value="{Binding Path=(m:togglebuttonimage.togglebuttonimagesource),RelativeSource={RelativeSource TemplatedParent}}" />
</Style>
</Image.Style>
</Image>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Application.Resources>
</Application>
I used attached propery in code:I also added namespace above
xmlns:m="clr-namespace:NoteProjectV2.classes.frmtopicclasses"
<ToggleButton Style="{StaticResource togglebutton_topic_menu_normal}" m:togglebuttonimage.togglebuttonimagesource="accept.png" />
But this is not working Where is my problem?
The first argument to the Register and RegisterAttached method of class DependencyProperty is the name of the property.
While you are using the name "ImageSource", it should actually be "ToggleButtonImageSource" (which already uses proper casing). Note also that as long as you only declare attached properties, the owning class does not need to be derived from DependencyObject.
public class ToggleButtonImage
{
public static readonly DependencyProperty ToggleButtonImageSourceProperty =
DependencyProperty.RegisterAttached(
"ToggleButtonImageSource", typeof(string), typeof(ToggleButtonImage));
public static void SetToggleButtonImageSource(UIElement element, string value)
{
element.SetValue(ToggleButtonImageSourceProperty, value);
}
public static string GetToggleButtonImageSource(UIElement element)
{
return (string)element.GetValue(ToggleButtonImageSourceProperty);
}
}
Besides that, you should better use ImageSource instead of string as the type of the property.
Related
I'm working on a CustomControl in WPF. I want to Implement a CardControl. I have two Contents "Front" and "Back" and I want to flip between them.
But I can't define a Working Trigger in the Style to flip between the contents...
First I created a Custom Control, with DependencyProperties:
public class Card : Control {
#region initializer
static Card() {
DefaultStyleKeyProperty.OverrideMetadata(typeof(Card), new FrameworkPropertyMetadata(typeof(Card)));
}
#endregion
#region dependencyProperties
public FrameworkElement CustomContent {
get { return (FrameworkElement)GetValue(CustomContentProperty); }
set { SetValue(CustomContentProperty, value); }
}
public static DependencyProperty CustomContentProperty =
DependencyProperty.Register(nameof(CustomContent), typeof(FrameworkElement), typeof(Card), new PropertyMetadata());
public FrameworkElement Front {
get { return (FrameworkElement)GetValue(FrontProperty); }
set { SetValue(FrontProperty, value); }
}
public static DependencyProperty FrontProperty =
DependencyProperty.Register(nameof(Front), typeof(FrameworkElement), typeof(Card), new PropertyMetadata());
public FrameworkElement Back {
get { return (FrameworkElement)GetValue(BackProperty); }
set { SetValue(BackProperty, value); }
}
public static DependencyProperty BackProperty =
DependencyProperty.Register(nameof(Back), typeof(FrameworkElement), typeof(Card), new PropertyMetadata());
public bool IsDetailed {
get { return (bool)GetValue(IsDetailedProperty); }
set { SetValue(IsDetailedProperty, value); }
}
public static readonly DependencyProperty IsDetailedProperty =
DependencyProperty.Register(nameof(IsDetailed), typeof(bool), typeof(Card), new PropertyMetadata(false));
public CornerRadius CornerRadius {
get { return (CornerRadius)GetValue(CornerRadiusProperty); }
set { SetValue(CornerRadiusProperty, value); }
}
public static readonly DependencyProperty CornerRadiusProperty =
DependencyProperty.Register(nameof(CornerRadius), typeof(CornerRadius), typeof(Card));
#endregion
Then I defined the Style in Generic.xaml:
<Style TargetType="{x:Type local:Card}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:Card}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"
Content="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:Card}}, Path=CustomContent,
Converter={StaticResource Debugger}, UpdateSourceTrigger=PropertyChanged}"/>
</Border>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource
AncestorType={x:Type local:Card}},
Path=isDetailed,
Converter={StaticResource Debugger}}"
Value="True">
<Setter Property="CustomContent"
Value="{Binding RelativeSource={RelativeSource
AncestorType={x:Type local:Card}},
Path=Back}"/>
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource
AncestorType={x:Type local:Card}},
Path=isDetailed}"
Value="False">
<Setter Property="CustomContent"
Value="{Binding RelativeSource={RelativeSource
AncestorType={x:Type local:Card}},
Path=Front}"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And I Implemented a Simple CardControl for Test-Purposes with a Button, which flips the isDetailed state:
<StackPanel>
<CustomCharts:Card Name="TestCard">
<CustomCharts:Card.CustomContent>
<TextBlock Text="Hello World!"/>
</CustomCharts:Card.CustomContent>
<CustomCharts:Card.Front>
<TextBlock Text="Wow vordere sache!"/>
</CustomCharts:Card.Front>
<CustomCharts:Card.Back>
<TextBlock Text="Wow hintere sache!"/>
</CustomCharts:Card.Back>
</CustomCharts:Card>
<Button Click="Button_Click" Width="50" Height="20" Content="Test"/>
</StackPanel>
private void Button_Click( object sender, RoutedEventArgs e ) {
this.TestCard.IsDetailed= !this.TestCard.IsDetailed;
}
Any Help is apreciated, I'm stuck...
you can benefit a lot if declare Front and Back with object type, not FrameworkElement - mostly because it will allow easy bidnings. you can keep rich visual appearance if you add corresponding templates for those two properties.
public class Card : Control
{
static Card()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Card), new FrameworkPropertyMetadata(typeof(Card)));
}
public object Front
{
get { return (object)GetValue(FrontProperty); }
set { SetValue(FrontProperty, value); }
}
public static readonly DependencyProperty FrontProperty =
DependencyProperty.Register("Front", typeof(object), typeof(Card), new PropertyMetadata(null));
public DataTemplate FrontTemplate
{
get { return (DataTemplate)GetValue(FrontTemplateProperty); }
set { SetValue(FrontTemplateProperty, value); }
}
public static readonly DependencyProperty FrontTemplateProperty =
DependencyProperty.Register("FrontTemplate", typeof(DataTemplate), typeof(Card), new PropertyMetadata(null));
public object Back
{
get { return (object)GetValue(BackProperty); }
set { SetValue(BackProperty, value); }
}
public static readonly DependencyProperty BackProperty =
DependencyProperty.Register("Back", typeof(object), typeof(Card), new PropertyMetadata(null));
public DataTemplate BackTemplate
{
get { return (DataTemplate)GetValue(BackTemplateProperty); }
set { SetValue(BackTemplateProperty, value); }
}
public static readonly DependencyProperty BackTemplateProperty =
DependencyProperty.Register("BackTemplate", typeof(DataTemplate), typeof(Card), new PropertyMetadata(null));
public bool IsDetailed
{
get { return (bool)GetValue(IsDetailedProperty); }
set { SetValue(IsDetailedProperty, value); }
}
public static readonly DependencyProperty IsDetailedProperty =
DependencyProperty.Register(nameof(IsDetailed), typeof(bool), typeof(Card), new PropertyMetadata(false));
public CornerRadius CornerRadius
{
get { return (CornerRadius)GetValue(CornerRadiusProperty); }
set { SetValue(CornerRadiusProperty, value); }
}
public static readonly DependencyProperty CornerRadiusProperty =
DependencyProperty.Register(nameof(CornerRadius), typeof(CornerRadius), typeof(Card), new PropertyMetadata(new CornerRadius(0)));
}
Use Trigger on IsDetailed property to flip Fron and Back:
<Style TargetType="{x:Type local:Card}">
<Setter Property="Background" Value="Khaki"/>
<Setter Property="BorderBrush" Value="Black"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="CornerRadius" Value="10"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:Card}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<ContentPresenter x:Name="presenter"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsDetailed" Value="True">
<Setter TargetName="presenter" Property="Content"
Value="{Binding Front, RelativeSource={RelativeSource TemplatedParent}}"/>
<Setter TargetName="presenter" Property="ContentTemplate"
Value="{Binding FrontTemplate, RelativeSource={RelativeSource TemplatedParent}}"/>
</Trigger>
<Trigger Property="IsDetailed" Value="False">
<Setter TargetName="presenter" Property="Content"
Value="{Binding Back, RelativeSource={RelativeSource TemplatedParent}}"/>
<Setter TargetName="presenter" Property="ContentTemplate"
Value="{Binding BackTemplate, RelativeSource={RelativeSource TemplatedParent}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
use TemplateBindings inside Template, and RelativeSource TemplatedParent with normal Binding in Setters (TemplateBinding is not supported in Setter).
And here is two examples of usage - with and without binding:
<StackPanel DataContext="{x:Static sys:DateTime.Now}">
<CheckBox Name="chk_1"/>
<local:Card Front="Front"
Back="Back"
IsDetailed="{Binding IsChecked, ElementName=chk_1}"/>
<CheckBox Name="chk_2"/>
<local:Card Front="{Binding DayOfWeek}"
Back="{Binding TimeOfDay}"
IsDetailed="{Binding IsChecked, ElementName=chk_2}">
<local:Card.FrontTemplate>
<DataTemplate>
<TextBlock Foreground="Red" FontWeight="Bold" FontSize="20"
Text="{Binding}" TextDecorations="Underline"/>
</DataTemplate>
</local:Card.FrontTemplate>
<local:Card.BackTemplate>
<DataTemplate>
<TextBlock Foreground="Blue" FontWeight="Bold" FontSize="20"
Text="{Binding}" TextDecorations="Underline"/>
</DataTemplate>
</local:Card.BackTemplate>
</local:Card>
</StackPanel>
result:
Let's say I have the following class:
public class MyClass : System.Windows.FrameworkElement
{
public static readonly DependencyProperty HasFocusProperty = DependencyProperty.RegisterAttached("HasFocus", typeof(bool), typeof(MyClass), new PropertyMetadata(default(bool)));
public bool HasFocus
{
get => (bool)GetValue(HasFocusProperty);
set => SetValue(HasFocusProperty, value);
}
public System.Windows.Controls.TextBox TextBox { get; set; }
}
I want to change some UI properties of TextBox via XAML Template Trigger based on the property HasFocus, so I do the following:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:win="clr-namespace:System.Windows.Controls">
<Style TargetType="{x:Type win:TextBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type win:TextBox}">
<ControlTemplate.Triggers>
<Trigger Property="MyClass.HasFocus" Value="True">
<Setter TargetName="Border" Property="BorderBrush" Value="Red" />
<Setter TargetName="Border" Property="BorderThickness" Value="2" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
However, the style is not applied when setting HasFocus = true.
Within the properties of TextBox, I can see that the trigger is registered. If I change <Trigger Property="MyClass.HasFocus" Value="True"> to <Trigger Property="MyClass.HasFocus" Value="False">, my style is applied initially. So I think my XAML definition is okay.
Any ideas how to solve this?
An element in a template that is applied to a TextBox cannot bind to a property of a MyClass, unless there is a MyClass element to bind to somewhere in the visual tree.
If you want to be able to set a custom HasFocus property of a TextBox, you should create an attached property:
public class FocusExtensions
{
public static readonly DependencyProperty SetHasFocusProperty = DependencyProperty.RegisterAttached(
"HasFocus",
typeof(bool),
typeof(FocusExtensions),
new FrameworkPropertyMetadata(false)
);
public static void SetHasFocus(TextBox element, bool value)
{
element.SetValue(SetHasFocusProperty, value);
}
public static bool GetHasFocus(TextBox element)
{
return (bool)element.GetValue(SetHasFocusProperty);
}
}
It can be set for any TextBox element:
<TextBox local:FocusExtensions.HasFocus="True">
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="local:FocusExtensions.HasFocus" Value="True">
<Setter Property="BorderBrush" Value="Red" />
<Setter Property="BorderThickness" Value="2" />
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
I have a c++ program that parse C# codes using Roslyn.
I need to convert my styles and custom controls to just "code-behind".
for example I have a simple custom control contains a button .
XAML Style :
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CUSTOM_LIBRARY_PARSE">
<Style TargetType="{x:Type local:CustomControl1}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomControl1}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Button Background="#FF487DF0" >
<Label VerticalContentAlignment="Center" HorizontalContentAlignment="Center" OpacityMask="#FFC3C3C3" Content="{Binding text_of_button_Value, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:CustomControl1}}}" />
</Button>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Code-Behind of Control :
using System;
using System.Windows;
using System.Windows.Controls;
namespace CUSTOM_LIBRARY_PARSE
{
public class CustomControl1 : Control
{
public static readonly DependencyProperty text_of_button
= DependencyProperty.Register(
"text_of_button_Value",
typeof(string),
typeof(CustomControl1),
new PropertyMetadata(Environment.UserName)
);
public string text_of_button_Value
{
get { return (string)GetValue(text_of_button); }
set { SetValue(text_of_button, value); }
}
static CustomControl1()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl1), new FrameworkPropertyMetadata(typeof(CustomControl1)));
}
}
}
Now , I need to know how to embed xaml code in code behind as a string like :
string code_xaml = "<ResourceDictionary\n xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n xmlns:local=\"clr-namespace:CUSTOM_LIBRARY_PARSE\">\n <Style TargetType=\"{x:Type local:CustomControl1}\">\n <Setter Property=\"Template\">\n <Setter.Value>\n <ControlTemplate TargetType=\"{x:Type local:CustomControl1}\">\n <Border Background=\"{TemplateBinding Background}\"\n BorderBrush=\"{TemplateBinding BorderBrush}\"\n BorderThickness=\"{TemplateBinding BorderThickness}\">\n <Button Background=\"#FF487DF0\" >\n <Label VerticalContentAlignment=\"Center\" HorizontalContentAlignment=\"Center\" OpacityMask=\"#FFC3C3C3\" Content=\"{Binding text_of_button_Value, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:CustomControl1}}}\" />\n </Button>\n </Border>\n </ControlTemplate>\n </Setter.Value>\n </Setter>\n </Style>\n</ResourceDictionary>\n";
And next parse it with XamlParser and load it to customcontrol1
is that possible ?
thanks
The Answer is :
Create a public version of custom control :
public CustomControl1()
{
}
Create a public version of custom control :
public CustomControl1()
{
ResourceDictionary Parse_Resource = XamlReader.Parse(code_xaml) as ResourceDictionary;
this.Resources = Parse_Resource;
}
Solved :D
I have a Button control that I want to be able to reuse throughout project. Each time button enters a new state, a different image will be displayed. For now, I have Normal State and Pressed State.
Here's the XAML portion of the control:
<Button
x:Class="customImageButton.ImageButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Button.Template>
<ControlTemplate TargetType="{x:Type Button}">
<Grid>
<ContentControl Width="80">
<Grid>
<Image Name="Normal" Source="{Binding NormalState}"/>
<Image Name="Pressed" Source="{Binding PressedState}" Visibility="Hidden"/>
</Grid>
</ContentControl>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="Normal" Property="Visibility" Value="Hidden"/>
<Setter TargetName="Pressed" Property="Visibility" Value="Visible"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Button.Template>
</Button>
Here's the code-behind for the control:
namespace customImageButton
{
public partial class ImageButton : Button
{
public ImageButton()
{
this.InitializeComponent();
}
public ImageSource NormalState
{
get { return base.GetValue(NormalStateProperty) as ImageSource; }
set { base.SetValue(NormalStateProperty, value); }
}
public static readonly DependencyProperty NormalStateProperty =
DependencyProperty.Register("NormalState", typeof(ImageSource), typeof(ImageButton));
public ImageSource PressedState
{
get { return base.GetValue(PressedStateProperty) as ImageSource; }
set { base.SetValue(PressedStateProperty, value); }
}
public static readonly DependencyProperty PressedStateProperty =
DependencyProperty.Register("PressedState", typeof(ImageSource), typeof(ImageButton));
}
}
...and here is its use:
<local:ImageButton Content="CustomButton" HorizontalAlignment="Left"
VerticalAlignment="Top" NormalState="Resources/Normal.png"
PressedState="Resources/Pressed.png"/>
My problem is that the images I've provided are not displaying. The Build Action for both images is Resource and I have tried using absolute path; however, that provided the same result. What am I missing?
Two problems with the code as listed:
The bindings need to be TemplateBinding.
The TargetType should refer to the "ImageButton" type, not Button.
Like this:
<ControlTemplate
xmlns:local="clr-namespace:customImageButton"
TargetType="{x:Type local:ImageButton}">
<Grid>
<ContentControl Width="80">
<Grid>
<Image Name="Normal" Source="{TemplateBinding NormalState}"/>
<Image Name="Pressed" Source="{TemplateBinding PressedState}" Visibility="Hidden"/>
</Grid>
</ContentControl>
</Grid>
Note: The above builds and runs, but Visual Studio complains:
'ImageButton' ControlTemplate TargetType does not match templated type 'Button'.
I suggest putting the control template in its own Style in the Themes/Generic.xaml resource dictionary, rather than inline.
My ListView is bound to an ObservableCollection, is there a way to position a button after the last listviewitem? What I have done is define the button in the DataTemplate like below:
<DataTemplate x:Key="TestDataTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="SeletedFilterText" Text="{Binding}" />
<Button Command="{Binding DataContext.TestCommand,ElementName=TestListView}"
Content="Test"
Visibility="{Binding Converter={StaticResource testConverter}}"
Grid.Column="1"/>
</Grid>
</DataTemplate>
In my ViewModel, I define a string variable to store the last item. The ItemSource(an Observable) may add or remove item, every time I set the last of the Collection to the LastItem variable. In the converter, compare the binding content with the LastItem, if the value is true, display the Button, if false, hide it. But the converter will never be triggered. Anyone can help?
I would suggest not to have backup field in ViewModel to keep track of lastItem in collection.
You can do that with only Converter in place which will return true or false if passed ListViewItem is last item in ListView or not.
In case you want to call the converter whenever underlying ObservableCollection add/remove item in it, I would suggest to pass Count property to converter so that converter gets fired whenever item is added/removed from collection.
Converter code:
public class IsLastItemInContainerConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter,
CultureInfo culture)
{
DependencyObject item = (DependencyObject)values[0];
ItemsControl ic = ItemsControl.ItemsControlFromItemContainer(item);
if (ic != null)
{
return ic.ItemContainerGenerator.IndexFromContainer(item)
== ic.Items.Count - 1;
}
else
return false;
}
public object[] ConvertBack(object value, Type[] targetTypes,
object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
XAML:
<Button Content="Test">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding
Converter="{StaticResource IsLastItemInContainerConverter}">
<Binding Path="."
RelativeSource="{RelativeSource Mode=FindAncestor,
AncestorType=ListViewItem}"/>
<Binding Path="DataContext.SourceCollection.Count"
RelativeSource="{RelativeSource Mode=FindAncestor,
AncestorType=ListView}"/>
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
Replace SourceCollection with your ObservableCollection name in dataTrigger.
I would suggest you create a custom control for your use case. Like so:
public class ButtonListView : ListView
{
static ButtonListView()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ButtonListView), new FrameworkPropertyMetadata(typeof(ButtonListView)));
}
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register(
"Command", typeof (ICommand), typeof (ButtonListView), new PropertyMetadata(default(ICommand)));
public ICommand Command
{
get { return (ICommand) GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public static readonly DependencyProperty ButtonContentProperty = DependencyProperty.Register(
"ButtonContent", typeof (object), typeof (ButtonListView), new PropertyMetadata(default(object)));
public object ButtonContent
{
get { return (object) GetValue(ButtonContentProperty); }
set { SetValue(ButtonContentProperty, value); }
}
}
And use this style:
<SolidColorBrush x:Key="ListBox.Disabled.Background" Color="#FFFFFFFF" />
<SolidColorBrush x:Key="ListBox.Disabled.Border" Color="#FFD9D9D9" />
<Style TargetType="{x:Type local:ButtonListView}" BasedOn="{StaticResource {x:Type ListBox}}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ButtonListView}">
<Border Name="Bd"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
SnapsToDevicePixels="true"
Padding="1">
<ScrollViewer Padding="{TemplateBinding Padding}"
Focusable="false">
<StackPanel>
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
<Button Content="{TemplateBinding ButtonContent}" Command="{TemplateBinding Command}"></Button>
</StackPanel>
</ScrollViewer>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter TargetName="Bd" Property="Background" Value="{StaticResource ListBox.Disabled.Background}" />
<Setter TargetName="Bd" Property="BorderBrush" Value="{StaticResource ListBox.Disabled.Border}" />
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsGrouping" Value="true" />
<Condition Property="VirtualizingPanel.IsVirtualizingWhenGrouping" Value="false" />
</MultiTrigger.Conditions>
<Setter Property="ScrollViewer.CanContentScroll" Value="false"/>
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
You can then use it like so:
<wpfSandbox:ButtonListView ButtonContent="Press" Command="{Binding ...}"/>
This way you don't need to Keep track of the order in the ObservableCollection
Yes and it's very easy:
Define your Observable collection with generic type of <DependencyObject>
Add a custom object to the end of the collection. (it can be a something like a ViewModel for Button if you want to add commands or etc to it)
Don't set the ItemTemplate of your ListView (or ItemsControl or etc)
Instead, define two DataTemplates without x:Key in the resources and set their DataType to the desired types. It should be like "{x:Type local:ButtonVm}" or "{x:Type vm:ListViewItemType}"
Now the template for each item automatically set to the data template that matches the type of that item.
Example:
(note that you can move ListView.Resources to Window.Resources if the templates can be reused elsewhere)
MainWindow.xaml:
<ListView ItemsSource="{Binding Items}">
<ListView.Resources>
<DataTemplate DataType="{x:Type vm:ListItemVm}">
<TextBlock Text="{Binding ItemText}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:ButtonVm}">
<Button Command="{Binding ButtonCommand}">
<TextBlock Text="{Binding ButtonText}"/>
</Button>
</DataTemplate>
</ListView.Resources>
</ListView>
MainWindow.xaml.cs:
public MainWindow()
{
InitializeComponent();
DataContext = this;
Items.Add(new ListItemVm { ItemText = "something" } );
Items.Add(new ListItemVm { ItemText = "something" } );
Items.Add(new ListItemVm { ItemText = "something" } );
Items.Add(new ButtonVm { ButtonText = "click here" } );
}
private ObservableCollection<DependencyObject> _items = new ObservableCollection<DependencyObject>();
public ObservableCollection<DependencyObject> Items { get { return _items; } }
one viewModel for each type of item:
public class ListItemVm : DependencyObject
{
public string ItemText
{
get { return (string)GetValue(ItemTextProperty); }
set { SetValue(ItemTextProperty, value); }
}
public static readonly DependencyProperty ItemTextProperty =
DependencyProperty.Register("ItemText", typeof(string), typeof(ListItemVm), new UIPropertyMetadata(""));
}
public class ButtonVm : DependencyObject
{
public string ButtonText
{
get { return (string)GetValue(ButtonTextProperty); }
set { SetValue(ButtonTextProperty, value); }
}
public static readonly DependencyProperty ButtonTextProperty =
DependencyProperty.Register("ButtonText", typeof(string), typeof(ButtonVm), new UIPropertyMetadata(""));
public Command ButtonCommand
{
get { return (string)GetValue(ButtonCommandProperty); }
set { SetValue(ButtonCommandProperty, value); }
}
public static readonly DependencyProperty ButtonCommandProperty =
DependencyProperty.Register("ButtonCommand", typeof(Command), typeof(ButtonVm), new UIPropertyMetadata(""));
}
public class Command : ICommand { /* simple implementation of ICommand */ }