I have a mvvm design pattern set up to do commands and workspaces so that a button is pressed and the proper workspace displays. The buttons have a template binding to pull in the proper image for the template based on the command. I have added another field to the command for a SelectedTemplateResource for when the button is selected out of the array of buttons. The DataTemplate is pulled into the mainwindow to display the buttons.
<DataTemplate x:Key="CommandsTemplate">
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Command="{Binding Path=Command}" Content="{Binding Path=DisplayName}" Template="{Utilities:BindableResource {Binding Path=TemplateResource}}">
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Command}">
<Setter Property="Template">
<Setter.Value>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
Want I want to do is set the value of the setter to the SelectedTemplateResource like so in the Template of the button called TemplateResource. These are all handled through the mainwindowviewmodel to set the appropriate template per button and it works well but I'm not sure how to finish the selected state.
Also, I'm not entirely sure if I should be setting the datatrigger binding to the command path.
Can anybody help me with figuring out how to set the selected state of a button using the design mentioned above?
Cheers.
EDIT
I get the use of the togglebutton trigger however the binding is not working. Heres some more information as for some reason I keep getting key is null error in my app.xaml.cs
The ToggleButton is a datatemplate in a file called MainWindowResources.xaml (ResourceDictionary).
The MainWindow.xaml file pulls in this data template.
I have a bindableresource class to help with the static resource for the resource file.
public class BindableResource : StaticResourceExtension
{
#region Fields
private static readonly DependencyProperty dummyProperty;
#endregion
#region Properties
/// <summary>
/// Gets and sets my binding.
/// </summary>
public Binding MyBinding { get; set; }
#endregion
#region Constructor
/// <summary>
/// Static contruction of the dummy dependency property.
/// </summary>
static BindableResource()
{
dummyProperty = DependencyProperty.RegisterAttached("Dummy", typeof(string), typeof(DependencyObject), new UIPropertyMetadata(null));
}
/// <summary>
/// Constructor.
/// </summary>
public BindableResource()
{
}
/// <summary>
/// Constructor with binding to set.
/// </summary>
/// <param name="binding"></param>
public BindableResource(Binding binding)
{
MyBinding = binding;
}
#endregion
#region External Members
/// <summary>
/// Get the resource key to bind to the resource.
/// </summary>
/// <param name="serviceProvider"></param>
/// <returns></returns>
public override object ProvideValue(IServiceProvider serviceProvider)
{
var target = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
var targetObject = (FrameworkElement)target.TargetObject;
MyBinding.Source = targetObject.DataContext;
var DummyDO = new DependencyObject();
BindingOperations.SetBinding(DummyDO, dummyProperty, MyBinding);
ResourceKey = DummyDO.GetValue(dummyProperty);
return base.ProvideValue(serviceProvider);
}
#endregion
}
However this does not work for the triggers setter value.
<DataTemplate>
<ToggleButton Command="{Binding Path=Command}" Content="{Binding Path=DisplayName}" Template="{Utilities:BindableResource {Binding Path=TemplateResource}}">
<ToggleButton.Style>
<Style TargetType="ToggleButton">
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Template" Value="{Utilities:BindableResource {Binding Path=SelectedTemplateResource}}" />
</Trigger>
</Style.Triggers>
</Style>
</ToggleButton.Style>
</ToggleButton>
</DataTemplate>
Any thoughts or ideas?
I'm not sure I've understood what you want exactly. You want to change the button's template if it's selected yes?
Firstly a standard button doesn't have a the notion of being "selected". You want a ToggleButton for that. That way you can trigger on its IsChecked property to set your template.
<DataTemplate x:Key="CommandsTemplate">
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ToggleButton Command="{Binding Path=Command}" Content="{Binding Path=DisplayName}" Template="{Utilities:BindableResource {Binding Path=TemplateResource}}">
<ToggleButton.Style>
<Style TargetType="ToggleButton">
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Template" Value="{Utilities:BindableResource {Binding DataContext.SelectedTemplateResource}}">
</Trigger>
</Style.Triggers>
</Style>
</ToggleButton.Style>
</ToggleButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
After finding out this really is not possible to do in pure xaml I brought out the c# and create a custom control... this is very basic and can be improved on and I will have change a bit of it but ultimately a custom control solves the issue so that you can hit the click event from within the resource dictionary and change the template on the fly.
public class TabButton : Button
{
public static readonly DependencyProperty SelectedTemplateProperty =
DependencyProperty.Register("SelectedTemplate", typeof(ControlTemplate), typeof(TabButton));
public ControlTemplate SelectedTemplate
{
get { return base.GetValue(SelectedTemplateProperty) as ControlTemplate; }
set { base.SetValue(SelectedTemplateProperty, value); }
}
public TabButton()
{
this.Click += new RoutedEventHandler(TabButton_Click);
}
~TabButton()
{
}
public void TabButton_Click(object sender, RoutedEventArgs e)
{
ControlTemplate template = (ControlTemplate)this.FindResource("Environmental Template Selected");
(sender as TabButton).Template = template;
}
}
Cheers.
Related
I have the following custom control based on the "heavy option" at this link:
public partial class SelectableContentControl : ContentControl
{
public SelectableContentControl()
{
InitializeComponent();
var isCheckedDesc = DependencyPropertyDescriptor.FromProperty(IsCheckedProperty, typeof(SelectableContentControl));
isCheckedDesc.AddValueChanged(this, IsCheckedPropertyChanged);
}
public bool IsChecked
{
get { return (bool)GetValue(IsCheckedProperty); }
set { SetValue(IsCheckedProperty, value); }
}
public static readonly DependencyProperty IsCheckedProperty =
DependencyProperty.Register("IsChecked", typeof(bool),
typeof(SelectableContentControl), new PropertyMetadata(false));
private void IsCheckedPropertyChanged(object sender, EventArgs e)
{
var selectable = Content as IAmSelectable;
if (selectable != null) selectable.IsSelected = IsChecked;
}
}
The style defined for the SelectableContentControl is as follows:
<Style TargetType="{x:Type controls1:SelectableContentControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls1:SelectableContentControl}">
<CheckBox IsChecked="{TemplateBinding IsChecked}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
...and my usage:
<controls:SelectableContentControl Grid.Row="2" Content="{Binding Dummy}" IsChecked="{Binding Dummy.IsChecked, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
I want IsCheckedPropertyChanged to be called whenever the IsChecked value changes on the UI, but this isn't happening. Anyone see what I'm missing?
TemplateBinding works in a OneWay mode, meaning that the value is updated only in source-to-target direction (your control being the source, and the CheckBox inside the template the target). If you want the binding to work in TwoWay mode, you should use a normal Binding instead:
<ControlTemplate TargetType="{x:Type controls1:SelectableContentControl}">
<CheckBox IsChecked="{Binding IsChecked, RelativeSource={RelativeSource TemplatedParent}}" />
</ControlTemplate>
Note that you don't need to specify Mode=TwoWay on the binding, because CheckBox.IsChecked property binds in two-way mode by default.
See this question for more detailed info.
I have a question about a listview setup like the example below. When I click the button below in the expander header I want that item to be selected as well, but what I'm seeing is while the button command does work, the item selected is still the previous item selected, not the item my button is in. How can I have the Item selected when the button is clicked?
I tried setting up a ControlTemplate like this, but it did not work.
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
<ControlTemplate.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="IsSelected" Value="True" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<ListView ItemsSource="{Binding MyItemSource,
Mode=TwoWay}"
SelectedItem="{Binding MySelectedItem,
Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate>
<Expander IsExpanded="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListViewItem}}, Path=IsSelected}">
<Expander.Header>
<Button Command={Binding MyCommand}>Click Me</Button>
</Expander.Header>
<!-- content here -->
</Expander>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I would suggest defining a command SelectItem in the main ViewModel which takes the item which is to be selected as a parameter. The execution method of this command can then set the MySelectedItem property, set a property IsSelected on the item ViewModel to true and invoke all further actions on the item itself (i.e. what is now executed by MyCommand). With the selection logic in the ViewModel and a clean binding you don't even need to use ListView at all but can stick to a plain ItemsControl:
The XAML then looks like this:
<ItemsControl ItemsSource="{Binding MyItemSource}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Expander IsExpanded="{Binding IsSelected}">
<Expander.Header>
<Button Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ItemsControl}, Path=DataContext.SelectItem}"
CommandParameter="{Binding"}>Click Me</Button>
</Expander.Header>
<!-- content here -->
</Expander>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The MainViewModel would look something like this:
public class MainViewModel : INotifyPropertyChanged
{
public ObservableCollection<ItemViewModel> MyItemsSource { get; private set; }
public ItemViewModel SelectedItem { get... set... }
public ICommand SelectItem { get; private set; }
ctor()...
private void ExecuteSelectItem(ItemViewModel item)
{
SelectedItem = item;
foreach (var i in MyItemsSource) i.IsSelected = false;
item.IsSelected = true;
item.DoSomething();
}
}
I have always found it way easier to use ItemsControl an implement the few lines of selection logic myself, instead of dealing with the messy binding of the selection of a ListView. In my opinion it is a quite intuitive to implement custom selection behavior (multiple items, allowing only certain combinations, etc.). You can use the IsSelected property easily to apply a custom styling of selected items.
You can try something like this in your view model, add if statement in setter:
private object _mySelectedItem;
public object MySelectedItem
{
get { return _mySelectedItem; }
set
{
if (_mySelectedItem != value && value != null)
{
_mySelectedItem = value;
OnPropertyChanged("MySelectedItem");
}
}
}
I have a view that contains multiple subview (UserControl).
<StackPanel>
<local:SubViewGPS/>
<local:SubViewText/>
</StackPanel>
This view binds to a ViewModel and I would like to load or not load subview depending on bool properties of my ViewModel
private bool isGPSCompatible;
public bool IsGPSCompatible {
get { return isGPSCompatible; }
set {
if (isGPSCompatible != value) {
isGPSCompatible = value;
NotifyPropertyChanged();
}
}
}
private bool isTextCompatible;
public bool IsTextCompatible {
get { return isTextCompatible; }
set {
if (isTextCompatible != value) {
isTextCompatible = value;
NotifyPropertyChanged();
}
}
}
I actually don't want to "Disable" or change the "Visibility" but really avoid to load the component if the property is false. According this post: Different views / data template based on member variable the combination of DataTemplate and DataTrigger seems to be a way to reach the goal but I was wondering if it exist something simpler. Thanks for your help
I finally used this solution:
<UserControl x:Class="RLinkClient.LocationView"
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:client"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<DataTemplate x:Key="GPSLocationViewTemplate">
<local:GPSControl/>
</DataTemplate>
<DataTemplate x:Key="NoGPSViewTemplate">
<TextBlock Text="GPS Disabled" TextAlignment="Center" VerticalAlignment="Center" FontStyle="Italic" Opacity="0.1" FontWeight="Bold" FontSize="14"/>
</DataTemplate>
</UserControl.Resources>
<Grid>
<ContentControl>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource NoGPSViewTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding GPSCapable}" Value="True">
<Setter Property="ContentTemplate" Value="{StaticResource GPSLocationViewTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</Grid>
</UserControl>
but Frank answer is totally acceptable if I could change the ViewModel structure.
As you're using MVVM, you could create separate sub-viewmodels for the different views and add them to some ObservableCollection, depending on their properties:
<!-- This would replace your StackPanel -->
<ItemsControl ItemsSource="{Binding Capabilities}">
<ItemsControl.Resources>
<DataTemplate DataType="localVm:GpsViewModel">
<local:SubViewGPS />
</DataTemplate>
<DataTemplate DataType="localVm:TextViewModel">
<local:SubViewText />
</DataTemplate>
<!-- ... -->
</ItemsControl.Resources>
</ItemsControl>
You would have an ObservableCollection in your view model, like this:
public ObservableCollection<ICapability> Capabilities { get; private set; }
and add sub-viewmodels implementing ICapability to this as needed.
Based on the condition, I suggest adding the view object in code instead of XAML. Only instantiate when needed to avoid unnecessary initialization routines. That is, don't instantiate the view before checking the condition:
if (IsGPSCompatible )
myStackPanel.Children.Add((new SubViewGPSView()));
if (IsTextCompatible )
myStackPanel.Children.Add((new SubViewText()));
I have a ResourceDictionary that has a Style responsible for creating the style of a vertical side menu.
Below is the XAML ResourceDictionary of a hypothetical example that was created to demonstrate the problem:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:control="clr-namespace:FirstFloor.ModernUI.Presentation">
<Style TargetType="control:Controle">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="control:Controle">
<Grid>
<Border Background="{TemplateBinding BackColor}">
<ListBox x:Name="LinkList" ItemsSource="{Binding Links,RelativeSource={RelativeSource TemplatedParent}}" Background="Transparent">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Height="50" Width="500" >
<TextBlock Text="{Binding DisplayName}" Foreground="Black" Margin="45,2,2,2" FontSize="{DynamicResource MediumFontSize}" TextTrimming="CharacterEllipsis" VerticalAlignment="Center" HorizontalAlignment="Left" />
</Grid>
<DataTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Trigger.Setters>
<Setter Property="control:Controle.BackColor" Value="Red"/>
</Trigger.Setters>
</Trigger>
</DataTemplate.Triggers>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The style is based on a class called Controle that inherits from Control.
Below is the code of this class:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace FirstFloor.ModernUI.Presentation
{
public class Controle:Control
{
/// <summary>
/// Identifies the Links dependency property.
/// </summary>
public static readonly DependencyProperty LinksProperty = DependencyProperty.Register("Links", typeof(LinkCollection), typeof(Controle), new PropertyMetadata(new LinkCollection()));
/// <summary>
/// Identifies the Links dependency property.
/// </summary>
public static readonly DependencyProperty BackColorProperty = DependencyProperty.Register("BackColor", typeof(SolidColorBrush), typeof(Controle), new PropertyMetadata( new SolidColorBrush(Color.FromRgb(0, 200,0))));
/// <summary>
/// Gets or sets the collection of links that define the available content in this tab.
/// </summary>
///
public LinkCollection Links
{
get { return (LinkCollection)GetValue(LinksProperty); }
set { SetValue(LinksProperty, value); }
}
/// <summary>
/// Gets or sets the collection of links that define the available content in this tab.
/// </summary>
public SolidColorBrush BackColor
{
get { return (SolidColorBrush)GetValue(BackColorProperty); }
set { SetValue(BackColorProperty, value); }
}
}
}
I wanna know why in the line below:
<Setter Property="control:Controle.BackColor" Value="Red"/>
I am not able to set a property of Controle...
The funny thing is that if I set the ban ownership of any other place, seem to take place, but when I'm inside the ItemTemplate by setting it has no effect.
IMHO the best you can do without wiring procedural code is:
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True" SourceName="LinkList">
<Trigger.Setters>
<Setter Property="BackColor" Value="Red" />
</Trigger.Setters>
</Trigger>
</ControlTemplate.Triggers>
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.