Selecting User Control for Data Template based on an Enum - wpf

I am working on a WPF app and currently I have an ItemsControl bound up to my View Model ObservableCollection and I have a DataTemplate that uses a UserControl to render the items on canvas. Can you use multiple User Controls and then switch which one is used based on an Enum? Another way to look it is to either create a Button or a TextBox for the item in the ObservableCollection based on an Enum.

You can select the data template for an item using a custom DataTemplateSelector. Assume we have the following:
public enum Kind
{
Button, TextBox,
}
public class Data
{
public Kind Kind { get; set; }
public string Value { get; set; }
}
Your data template selector might then look like this:
public class MyTemplateSelector : DataTemplateSelector
{
public DataTemplate ButtonTemplate { get; set; }
public DataTemplate TextBoxTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
Data data = (Data)item;
switch (data.Kind)
{
case Kind.Button:
return ButtonTemplate;
case Kind.TextBox:
return TextBoxTemplate;
}
return base.SelectTemplate(item, container);
}
}
In XAML, declare templates for all the cases you want to cover, in this case buttons and text boxes:
<Window.Resources>
<ResourceDictionary>
<DataTemplate x:Key="ButtonTemplate" DataType="local:Data">
<Button Content="{Binding Value}" />
</DataTemplate>
<DataTemplate x:Key="TextBoxTemplate" DataType="local:Data">
<TextBox Text="{Binding Value}" />
</DataTemplate>
</ResourceDictionary>
</Window.Resources>
Finally, have your ItemsControl create an instance of your custom template selector, initializing its two DataTemplateproperties from the above data templates:
<ItemsControl>
<ItemsControl.ItemTemplateSelector>
<local:MyTemplateSelector
ButtonTemplate="{StaticResource ButtonTemplate}"
TextBoxTemplate="{StaticResource TextBoxTemplate}"/>
</ItemsControl.ItemTemplateSelector>
<ItemsControl.Items>
<local:Data Kind="Button" Value="1. Button" />
<local:Data Kind="TextBox" Value="2. TextBox" />
<local:Data Kind="TextBox" Value="3. TextBox" />
<local:Data Kind="Button" Value="4. Button" />
</ItemsControl.Items>
</ItemsControl>
(In real life, set the ItemsSource instead of declaring the items inline, as I did.)
For completeness: To access your C# classes you need to set up the namespace, e.g.,
xmlns:local="clr-namespace:WPF"

Another possible quick solution is to use Data Triggers:
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="Content"
Value="{StaticResource YourDefaultLayout}" />
<Style.Triggers>
<DataTrigger Binding="{Binding YourEnumVMProperty}"
Value="{x:Static local:YourEnum.EnumValue1}">
<Setter Property="Content"
Value="{StaticResource ContentForEnumValue1}" />
</DataTrigger>
<DataTrigger Binding="{Binding YourEnumVMProperty}"
Value="{x:Static local:YourEnum.EnumValue2}">
<Setter Property="Content"
Value="{StaticResource ContentForEnumValue2}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
You could also define the template of a whole control using a trigger setter.
I prefer this because there is no need to define all the DataTemplateSelector stuff etc.

Related

WPF DataBinding: How to bind "IsEnabled" of a ComboBoxItem if the ComboBox "ItemsSource" Property is used?

Currently I'm creating a custom Style for a ComboBox.
Current State of Styling
The next step should be the IsEnabled state of the ComboBoxItems. Therefore I created a Simple User Class and a UserList ObservableCollection bound to the ComboBox.
public class User
{
public int Id { get; private init; }
public string Name { get; private init; }
public bool IsEnabled { get; private init; }
public User(int id, string name, bool isEnabled = true)
{
Id = id;
Name = name;
IsEnabled = isEnabled;
}
}
<ComboBox
ItemsSource="{Binding UserList}"
DisplayMemberPath="Name"
SelectedItem="{Binding SelectedUser}"
IsEnabled="{Binding IsComboboxEnabled}"
IsEditable="{Binding IsComboboxEditable}"
/>
To create and test the Disabled Style of the ComboBoxItems I want to Bind the IsEnabled Property of the User to the IsEnabled Property of the ComboBoxItem.
But I can't use a ItemContainerStyle here, because this overrides my custom Style:
<ComboBox
...
>
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="IsEnabled" Value="{Binding IsEnabled}" />
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
So: How can I bind the IsEnabled Property without using a ItemContainerStyle or destroying the custom style I already add to the ComboBox?
If you have a custom ComboBoxItem Style that you don't want to override, then your Style inside the ItemContainerStyle should have a BasedOn, which will basically copy your default style, then add/replace with whatever is contained:
<Style TargetType="ComboBoxItem" BasedOn="YourComboBoxItemStyle">
Otherwise, if you have a ComboBox Style and want to add this then in either your Style in your ResourceDictionary you can add a Style.Resources in the Style that targets the ComboBoxItem:
<Style TargetType="ComboBox" x:Key="MyComboBoxStyle">
<Setter .../>
<Setter .../>
<Style.Resources>
<Style TargetType="ComboBoxItem">
<Setter Property="IsEnabled" Value="{Binding IsEnabled}" />
</Style>
</Style.Resources>
</Style>
I hope I understood the question and that my answer is helpful.

Automatically inherit control styles in only one custom window

I am wondering if it is possible to associate Styles for certain controls with a custom window in WPF.
Here's the scenario - I have created a custom window, and have defined styles for a number of controls that I will use in this window. These are contained in a portable class library.
The catch is that I only want the controls to use the style from my library when they are used in the custom window (there are several different windows in the application).
I understand that I can assign the styles a key, and load them from my portable library in my application's app.xaml using pack syntax, for example:
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Custom.Application.Library.Controls;component/Styles/CheckBox.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
And then add and style the control within my custom window as such:
<CheckBox x:Name="checkBox" Style="{StaticResource SpecialCheckBox}"
But what I would really like to do is define they styles in my class library without a key, as in this:
<Style TargetType="{x:Type CheckBox}">
Instead of this:
<Style x:Key="SpecialCheckBox" TargetType="{x:Type CheckBox}">
So that when this checkbox is used in my custom window it automatically inherits the style. If I define the style like this, and load it into my app.xaml, the problem is obviously that ALL checkboxes will inherit this style, not just checkboxes used in my custom window.
So, what I'm trying to find out is if there is any way to associate a style resource explicitly with a custom window, so that I can define the styles without a key, and have them by default inherit the "Special" style when used in my custom window, but use the WPF defaults in any other windows of the application. Does anyone have experience with this?
For clarity here is the code of my custom window:
XAML:
<!-- Window style -->
<Style TargetType="{x:Type Controls:CCTApplicationWindow}">
<Setter Property="WindowStyle" Value="None"/>
<Setter Property="AllowsTransparency" Value="True"/>
<Setter Property="ResizeMode" Value="CanResizeWithGrip"/>
<Setter Property="MinWidth" Value="500"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Controls:CCTApplicationWindow}">
<Border BorderBrush="#FF999999">
<Border.Style>
<Style TargetType="{x:Type Border}">
<Setter Property="BorderThickness" Value="1"/>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=WindowState}" Value="Maximized">
<Setter Property="BorderThickness" Value="7"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<Grid>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="29"/>
<RowDefinition />
</Grid.RowDefinitions>
<Controls:CCTApplicationHeader Grid.Row="0"
Margin="0"
Title="{TemplateBinding Title}"
DragMoveCommand="{TemplateBinding DragMoveCommand}"
MaximizeCommand="{TemplateBinding MaximizeCommand}"
MinimizeCommand="{TemplateBinding MinimizeCommand}"
CloseCommand="{TemplateBinding CloseCommand}"/>
<Grid Background="White" Grid.Row="1" Margin="0">
<AdornerDecorator>
<ContentPresenter/>
</AdornerDecorator>
</Grid>
</Grid>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
CS:
public partial class CCTApplicationWindow : Window
{
public static readonly DependencyProperty MaximizeCommandProperty = DependencyProperty.Register("MaximizeCommand", typeof(DelegateCommand), typeof(CCTApplicationWindow));
public static readonly DependencyProperty MinimizeCommandProperty = DependencyProperty.Register("MinimizeCommand", typeof(DelegateCommand), typeof(CCTApplicationWindow));
public static readonly DependencyProperty CloseCommandProperty = DependencyProperty.Register("CloseCommand", typeof(DelegateCommand), typeof(CCTApplicationWindow));
public static readonly DependencyProperty DragMoveCommandProperty = DependencyProperty.Register("DragMoveCommand", typeof(DelegateCommand), typeof(CCTApplicationWindow));
public CCTApplicationWindow()
{
MaximizeCommand = new DelegateCommand(MaximizeExecute);
MinimizeCommand = new DelegateCommand(MinimizeExecute);
CloseCommand = new DelegateCommand(CloseExecute);
DragMoveCommand = new DelegateCommand(DragMoveExecute);
}
static CCTApplicationWindow()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CCTApplicationWindow), new FrameworkPropertyMetadata(typeof(CCTApplicationWindow)));
}
public DelegateCommand MaximizeCommand
{
get
{
return (DelegateCommand)GetValue(MaximizeCommandProperty);
}
set
{
SetValue(MaximizeCommandProperty, value);
}
}
public DelegateCommand MinimizeCommand
{
get
{
return (DelegateCommand)GetValue(MinimizeCommandProperty);
}
set
{
SetValue(MinimizeCommandProperty, value);
}
}
public DelegateCommand CloseCommand
{
get
{
return (DelegateCommand)GetValue(CloseCommandProperty);
}
set
{
SetValue(CloseCommandProperty, value);
}
}
public DelegateCommand DragMoveCommand
{
get
{
return (DelegateCommand)GetValue(DragMoveCommandProperty);
}
set
{
SetValue(DragMoveCommandProperty, value);
}
}
private void MaximizeExecute(object obj)
{
if (this.WindowState != WindowState.Maximized)
{
this.WindowState = WindowState.Maximized;
}
else
{
SystemCommands.RestoreWindow(this);
}
}
private void MinimizeExecute(object obj)
{
SystemCommands.MinimizeWindow(this);
}
private void CloseExecute(object obj)
{
SystemCommands.CloseWindow(this);
}
private void DragMoveExecute(object obj)
{
DragMove();
}
}
Yes, you can do this, but you shouldn't! You've tagged this question as MVVM and yet your architecture design breaks MVVM entirely. The whole point of MVVM is that view logic is contained within the view model layer; your view models are the ones that should be keeping track of the logical hierarchy and they are the ones that should be exposing properties to the views to control their appearance. To put it another way, just because XAML is flexible enough and powerful enough to implement such logic doesn't mean it's the best place to actually do it!
To answer your question though, yes, this can be done with a DataTrigger binding to the parent with ObjectToTypeConverter. Here's an example of setting the TextBlock background to CornflowerBlue, unless its immediate parent is a Grid in which case it should be set to PaleGoldenrod:
<StackPanel Orientation="Vertical">
<StackPanel.Resources>
<converters:ObjectToTypeConverter x:Key="ObjectToTypeConverter" />
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Background" Value="CornflowerBlue" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Parent, RelativeSource={RelativeSource Mode=Self}, Converter={StaticResource ObjectToTypeConverter}}" Value="{x:Type Grid}">
<Setter Property="Background" Value="PaleGoldenrod" />
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Resources>
<Grid Width="100" Height="32" HorizontalAlignment="Left">
<TextBlock Text="TextBox A" /> <!-- Gets a PaleGoldenrod background -->
</Grid>
<Canvas Width="100" Height="32" HorizontalAlignment="Left">
<TextBlock Text="TextBox B" /> <!-- Gets a CornflowerBlue background -->
</Canvas>
</StackPanel>
And here's the converter code. It's worth pointing out that if you're happy to simply check that a parent of a given type exists somewhere in the hierarchy (as opposed to the immediate parent) then you don't even need this, you can just attempt to bind to RelativeSource with AncestorType set to the relevant parent type.
// based on http://stackoverflow.com/questions/8244658/binding-to-the-object-gettype
[ValueConversion(typeof(object), typeof(Type))]
public class ObjectToTypeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value == null ? null : value.GetType();
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new InvalidOperationException();
}
}
But again, I implore you, if you really do want to adhere to MVVM then do not do it like this! This is exactly the kind of problem that "proper" MVVM was designed to solve.
Simplest way is to create a separate ResourceDictionary for your Custom window. And use it either using XAML or load it using Code.

Content control, ContentTemplateSelector, how to choose a staticresource based on value comming through binding, without datatriggers

In a resource dictionary, I have few Viewboxes, and a datatemplate which has content control in it. I’m using style to switch between the viewboxes like this
<DataTemplate x:Key="foo">
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding Viewbox}" Value="SageCashYes">
<Setter Property="Content" Value="{StaticResource SageCashYesViewbox}" />
</DataTrigger>
<DataTrigger Binding="{Binding Viewbox}" Value="SageCashNo">
<Setter Property="Content" Value="{StaticResource SageCashNoViewbox}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
Is there anyway to do something like this
<DataTemplate x:Key="foo">
<ContentControl ContentTemplateSelector="{Binding Viewbox}" >
</DataTemplate>
So I’d like to be able to tell it to choose appropriate Viewbox based on this binding but without these data triggers?
I’m asking it because the view boxes have vectors inside to draw images, and these data trigger will grow huge in no time. So if there is something out there which will make my life easier I’d like to know about it.
EDIT
<ListBox ItemTemplate="{StaticResource Foo}
ItemsSource="{Binding MyList}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
public class MyViewModel
{
public MyViewModel()
{
MyList = new List<MyList>()
{
new MyItem() {BoolValue = false, Heading = "Bank Payment", Viewbox = "SageCashNo"},
new MyItem() {BoolValue = true, Heading = "Cash Payment", Viewbox = "SageCashYes"}
};
}
public List<MyItem> MyList { get; set; }
}
public class MyItem
{
public bool {BoolValue { get; set; }
public string Heading { get; set; }
public string Viewbox { get; set; }
}
Kind Regards
Daniel

stuck to call view model from view

I have a tree view like below added mousedouble click
<TreeView
Grid.Row="0"
Name="tvTopics"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
MouseDoubleClick="tvTopics_MouseDoubleClick"
ItemsSource="{Binding TierOneItems}"
SelectedItemChanged="treeView1_SelectedItemChanged">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding Topic.IsSelected, Mode=TwoWay}" />
<Setter Property="FontWeight" Value="Normal" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
<Trigger Property="IsExpanded" Value="True">
<Setter Property="IsSelected" Value="True" />
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
on my code behind
private void tvTopics_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
TreeView tv = sender as TreeView;
if (tv.SelectedItem is TopicTreeItemViewModel)
{
Model.SelectedTopic = ((TopicTreeItemViewModel)tv.SelectedItem).Topic;
}
}
here i am trying to pass my "topic" value to my view model but i have no idea how to pass or call my view model method.
public class TopicTreeViewModel : NotificationObject, ITopicTreeViewModel
{
[ImportingConstructor]
public TopicTreeViewModel(IGatewayService storyService, IEventAggregator eventAggregator)
{
this.storyService = storyService;
this.eventAggregator = eventAggregator;
this.AddTopicCommand = new DelegateCommand<object>(this.AddTopic);
Helper.SubscriptionTokenList_LocationSearch.Add(this.eventAggregator.GetEvent<LocationSearchEvent>().Subscribe(OnLocationSearch, ThreadOption.UIThread));
Helper.SubscriptionTokenList_SubjectSearch.Add(this.eventAggregator.GetEvent<SubjectSearchEvent>().Subscribe(OnSubjectSearch, ThreadOption.UIThread));
}
public void MouseDoubleClick(Topic topic)
{
if (topic != null && topic is Topic)
{
switch (this.searchType)
{
case SearchType.Location:
this.eventAggregator.GetEvent<AddLocationEvent>().Publish((Topic)topic);
break;
case SearchType.Subject:
this.eventAggregator.GetEvent<AddSubjectEvent>().Publish((Topic)topic);
break;
}
}
}
And the interface connect between view and view model
public interface ITopicTreeViewModel
{
ReadOnlyCollection<TopicTreeItemViewModel> TierOneItems { get; }
ICommand SearchCommand { get; }
string SearchText { get; set; }
Topic SelectedTopic { get; set; }
}
All im trying to do here is passing the topic value to my view model when the mousedouble click event triggered.
I have no idea how to pass or bind this value. any help much appreciated.
When using Prism and MVVM in particular, it is reccomended to add the minimal code behind implementation as possible. Therefore, every logic or action performed would be handled directly into the ViewModel.
Instead of handling the event on the View's Code Behind, you should bind the MouseDoubleClick event to a Delegate Command in the ViewModel. So, in order to achieve this, you would need to set the proper ViewModel as the DataContext of the View. This way, Binding would be resolved through the DataContext implementation.
The following MSDN Prism Guide chapter would be helpful to understand the interaction between View and ViewModel:
Implementing the MVVM pattern
Advance MVVM scenarios
In addition, you could take a look at the MVVM Prism QuickStart and undestand how the Binding to the View-ViewModel interaction is implemented.
I hope this helped, Regards.

Bind two elements' Visibility to one property

I have two Menu Item elements - "Undelete" and "Delete" who have complementary visibility: when one is shown, the other one is hidden.
In the code of the ViewModel I have a dependency property FilesSelectedCanBeUndeleted defined as below:
private bool _filesSelectedCanBeUndeleted;
public bool FilesSelectedCanBeUndeleted
{
get
{
return _filesSelectedCanBeUndeleted;
}
set
{
_filesSelectedCanBeUndeleted = value;
OnPropertyChanged("FilesSelectedCanBeUndeleted");
}
}
the XAML for the Undelete button looks like below:
<MenuItem Header="Undelete" Command="{Binding UndeleteCommand }"
Visibility="{Binding Path=FilesSelectedCanBeUndeleted,
Converter={StaticResource BoolToVisConverter}}" >
As you can see the Visibility of the Undelete is bind to the FilesSelectedCanBeUndeleted
property ( with the help of a BooleanToVisibilityConveter).
Now my question is, how can I write the XAML to bind the Visibility of the Delete button to the "NOT" value of the FilesSelectedCanBeUndeleted property?
Thanks,
Here is an example of a custom IValueConverter, that allows you to reverse the visibility logic. Basically, one MenuItem will be visible when your view-model property is true, and the other would be collapsed.
So you'd need to define two instances of the converter like so:
<local:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
<local:BooleanToVisibilityConverter x:Key="ReversedBooleanToVisibilityConverter" IsReversed="true" />
You can use apply the datatrigger to you menuitem to avoid another property in your viemodel like this -
<MenuItem Header="Delete"
Command="{Binding DeleteCommand }">
<MenuItem.Style>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding FilesSelectedCanBeUndeleted}" Value="False">
<Setter Property="Visibility"
Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</MenuItem.Style>
</MenuItem>
Create new property on your ViewModel and just Negate 'FilesSelectedCanBeUndeleted' and then bind to it.
I did something like this a while ago with a simple negation...
private bool _filesSelectedCanBeUndeleted;
public bool FilesSelectedCanBeUndeleted{
get{
return _filesSelectedCanBeUndeleted;
}
set{
_filesSelectedCanBeUndeleted = value;
OnPropertyChanged("FilesSelectedCanBeUndeleted");
// You have also to notify that the second Prop will change
OnPropertyChanged("FilesSelectedCanBeDeleted");
}}
public bool FilesSelectedCanBeDeleted{
get{
return !FilesSelectedCanBeUndeleted;
}
}
Xaml could look like this then ....
<MenuItem Header="Delete"
Command="{Binding DeleteCommand }"
Visibility="{Binding Path=FilesSelectedCanBeDeleted, Converter={StaticResource BoolToVisConverter}}" >

Resources