I'm working on a new application that use a lot of reflection and have some problem to populate a combobox.
I have a parameter class with some information about a parameter for specific method:
class Parameter
{
public string Name { get; set; }
public Type Type { get; set; }
public object Value { get; set; }
}
I also have a view with a DataTemplateSelector that pick different DataTemplated depending on the parameter type:
class ParameterDataTemplateSelector : DataTemplateSelector
{
public DataTemplate StringDataTemplate { get; set; }
public DataTemplate BoolDataTemplate { get; set; }
public DataTemplate EnumDataTemplate { get; set; }
public DataTemplate NullTypeDataTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var parameter = item as Parameter;
if (parameter.Type == null)
{
return NullTypeDataTemplate;
}
if (parameter.Type == typeof (int)
|| parameter.Type == typeof(string))
{
return StringDataTemplate;
}
if (parameter.Type.BaseType == typeof (Enum))
{
return EnumDataTemplate;
}
return BoolDataTemplate;
}
}
And in my XAML I have my different templates:
<UserControl x:Class="Stuff.UI.Views.ParametersView"
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:dataTemplateSelectors="clr-namespace:Stuff.UI.Common.DataTemplateSelectors"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<DataTemplate x:Key="StringDataTemplate">
<StackPanel>
<Label Content="{Binding Name}"/>
<TextBox Text="{Binding Value}" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="BoolDataTemplate">
<StackPanel>
<CheckBox Content="{Binding Name}" IsChecked="{Binding Value}"/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="EnumDataTemplate">
<StackPanel>
<ComboBox ItemsSource="{Binding >>something<<}"/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="NullDataTemplate">
<StackPanel>
<Label Content="NULL TYPE"/>
</StackPanel>
</DataTemplate>
<dataTemplateSelectors:ParameterDataTemplateSelector
StringDataTemplate="{StaticResource StringDataTemplate}"
BoolDataTemplate="{StaticResource BoolDataTemplate}"
EnumDataTemplate="{StaticResource EnumDataTemplate}"
NullTypeDataTemplate="{StaticResource NullDataTemplate}"
x:Key="ParameterDataTemplateSelector"/>
</UserControl.Resources>
<Grid x:Name="MainGrid">
<StackPanel Background="#dddddd">
<Label Content="Parameters" Background="#e1db45"></Label>
<ScrollViewer VerticalScrollBarVisibility="Auto" Height="Auto" Margin="10">
<ItemsControl ItemsSource="{Binding Parameters}" ItemTemplateSelector="{StaticResource ParameterDataTemplateSelector}">
</ItemsControl>
</ScrollViewer>
</StackPanel>
</Grid>
</UserControl>
So the problem is my combobox here:
<DataTemplate x:Key="EnumDataTemplate">
<StackPanel>
<ComboBox ItemsSource="{Binding >>something<<}"/>
</StackPanel>
</DataTemplate>
How can I populate it when I only have a type (and I know by checking that it is a Enum type)? Or should I do it in code-behind in some way? I'm a bit lost here and pretty new to MVVM and WPF in general.
You could use a converter to get the list of enum values from the enum type. Something like this:
<ComboBox ItemsSource="{Binding Type,Converter={StaticResource EnumToValuesConverter}}">
<ComboBox.Resources>
<ResourceDictionary>
<local:EnumToValuesConverter x:Key="EnumToValuesConverter" />
</ResourceDictionary>
</ComboBox.Resources>
</ComboBox>
The converter should just call "GetEnumValues" on the enum type:
public class EnumToValuesConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var enumType = (Type)value;
return enumType.GetEnumValues();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Try to look at this:
Bind to an enumeration
You should use ObjectDataProvider.
Related
I'm writing a program that dynamically creates Control based on the data type of the properties extracted using reflection. Here is the view in subject for examination.
<ListView ItemsSource="{Binding PropertyControls}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="8">
<TextBlock Text="{Binding PropertyName}" FontSize="14" Width="400"></TextBlock>
<UserControl FontSize="14" Content="{Binding Path=PropertyValue, Converter={StaticResource PropertyValueConverter}}"></UserControl>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I created an item template for the items in ListView. Each row consists of two elements; the label and the dynamically created control.
For instance, if the PropertyValue is a boolean, then the dynamically created control will be a checkbox. If the PropertyValue is a string, then the dynamically created control will be a TextBox. If the PropertyValue is a list of FileInfo, then a separate window will be created with another ListView and browse button with OpenFileDialog.
I was able to accomplish the dynamically created control by creating a class that implements IValueConverter and which is utilized as specified in the XAML. The PropertyValueConverter converts the PropertyValue into a dynamically created control by inspecting its data type.
My problem is when the CheckBox is checked, there was no event raised and the ViewModel is not modified by its changes. I suspect because the binding in the XAML was made to the UserControl and not to its child control which happens to be a CheckBox. Although it is possible to bind the IsChecked programmatically in the PropertyValueConverter, is there a better way to solve this?
------- Revision 1 -------
public class PropertyControl: INotifyPropertyChanged
{
public string PropertyName { get; set; }
private object propertyValue;
public object PropertyValue
{
get { return propertyValue; }
set
{
propertyValue = value;
OnPropertyChanged(nameof(PropertyValue));
}
}
#region INotifyPropertyChanged Implementation
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
/// <summary>
/// Dynamically converts between value and control given a data type - control mapping.
/// </summary>
class PropertyValueConverter: IValueConverter
{
/// <summary>
/// Converts from value to control.
/// </summary>
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (targetType == typeof (int))
return new NumberTextBox {Text = value.ToString()};
if (targetType == typeof (string))
return new TextBox {Text = value.ToString()};
if (targetType == typeof (bool))
return new CheckBox {IsChecked = (bool) value};
throw new Exception("Unknown targetType: " + targetType);
}
/// <summary>
/// Converts from control to value.
/// </summary>
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (targetType == typeof (NumberTextBox))
return (value as NumberTextBox).Value;
if (targetType == typeof(TextBox))
return (value as TextBox).Text;
if (targetType == typeof(CheckBox))
return (value as CheckBox).IsChecked;
throw new Exception("Unknown targetType: " + targetType);
}
}
------- Revision 2 -------
public partial class SettingsWindow : Window
{
public BindingList<SettingViewModel> ViewModels { get; set; }
private SettingsManager settingsManager = new SettingsManager(new SettingsRepository());
public SettingsWindow()
{
InitializeComponent();
// Reloads the data stored in all setting instances from database if there's any.
settingsManager.Reload();
// Initialize setting view model.
ViewModels = SettingViewModel.GetAll(settingsManager);
}
private void ResetButton_OnClick(object sender, RoutedEventArgs e)
{
settingsManager.Reload();
}
private void SaveButton_OnClick(object sender, RoutedEventArgs e)
{
settingsManager.SaveChanges();
}
}
--- Tab Control ---
<TabControl Name="ClassTabControl" TabStripPlacement="Left" ItemsSource="{Binding ViewModels}">
<TabControl.Resources>
<utilities:PropertyValueConverter x:Key="PropertyValueConverter" />
</TabControl.Resources>
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding DisplayName}"
Margin="8" FontSize="14"></TextBlock>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListView ItemsSource="{Binding PropertyControls}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="8">
<TextBlock Text="{Binding PropertyName}" FontSize="14" Width="400"></TextBlock>
<CheckBox FontSize="14" IsChecked="{Binding Path=PropertyValue, Converter={StaticResource PropertyValueConverter}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></CheckBox>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<StackPanel Orientation="Horizontal" Grid.Row="1" Margin="8" HorizontalAlignment="Center">
<Button Name="ResetButton" Padding="4" Content="Reset" FontSize="14" Margin="4"
Click="ResetButton_OnClick"></Button>
<Button Name="SaveButton" Padding="4" Content="Save" FontSize="14" Margin="4"
Click="SaveButton_OnClick"></Button>
</StackPanel>
</Grid>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
A much easier way is to create templates based on the type of your property. First of all you have to add the system namespace to access all the basic types:
xmlns:System="clr-namespace:System;assembly=mscorlib"
Now you can get rid of your converter and do it all in XAML like:
<DataTemplate>
<StackPanel x:Name="itemStackPanel" Orientation="Horizontal" Margin="8">
<!-- General part -->
<TextBlock Text="{Binding PropertyName}" FontSize="14" Width="400"/>
<!-- Specific (property based) part -->
<ContentPresenter Content="{Binding PropertyValue}">
<ContentPresenter.Resources>
<DataTemplate DataType="{x:Type System:String}">
<TextBlock Text="{Binding ElementName=itemStackPanel, Path=DataContext.PropertyValue}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type System:Boolean}">
<CheckBox IsChecked="{Binding ElementName=itemStackPanel, Path=DataContext.PropertyValue}"/>
</DataTemplate>
<!-- ... -->
</ContentPresenter.Resources>
</ContentPresenter>
</StackPanel>
</DataTemplate>
You simply create a template for every possible type like you need it. The ContentPresenter selects the right template based on the type of PropertyValue. Since you are going to bind to a parent from out of your template you have to use a element to bind on PropertyValue (described in Access parent DataContext from DataTemplate).
/edit Ok, some one was faster :/
Here's the example (without INotifyPropertyChanged because I did not want to write too much code ;))
public interface IViewModel
{
string PropertyName { get; set; }
}
public class StringViewModel : IViewModel
{
public string PropertyName { get; set; }
public string Content { get; set; }
}
public class BooleanViewModel : IViewModel
{
public string PropertyName { get; set; }
public bool IsChecked { get; set; }
}
public class MainViewModel
{
public ObservableCollection<IViewModel> ViewModels { get; set; }
public MainViewModel()
{
ViewModels = new ObservableCollection<IViewModel>
{
new BooleanViewModel {PropertyName = "Bool", IsChecked = true },
new StringViewModel {PropertyName = "String", Content = "My text"}
};
}
}
<Window x:Class="WpfApplication2.MainWindow"
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:WpfApplication2"
mc:Ignorable="d"
xmlns:viewModel="clr-namespace:WpfApplication2"
Title="MainWindow">
<Grid>
<ListView ItemsSource="{Binding ViewModels}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal"
Margin="8">
<TextBlock Text="{Binding PropertyName}" />
<ContentControl FontSize="14" Content="{Binding .}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type viewModel:StringViewModel}">
<TextBox Text="{Binding Content}" />
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:BooleanViewModel}">
<CheckBox IsChecked="{Binding IsChecked}" />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Window>
How binding quiz with different type questions in WPF app using MVVM?
QuizPageViewModel:
public class QuizPageViewModel : ViewModelBase
{
public QuizPageViewModel()
{
QuizCollection = new ObservableCollection<QuizQuestion>();
}
public ObservableCollection<QuizQuestion> QuizCollection { get; set; }}
Where QuizQuestion: - EF Entity
public partial class QuizQuestion
{
public QuizQuestion()
{
QuizAnswers = new HashSet<QuizAnswer>();
QuizMultiQuestions = new HashSet<QuizMultiQuestion>();
}
public long Id { get; set; }
public int QuizId { get; set; }
***public String Type { get; set; }***
[Required]
public string Name { get; set; }
}
Question Type can be truefalse, multianswer, multichoice and other type
in xaml:
<ItemsControl ItemsSource="{Binding QuizCollection}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ListBox ItemsSource="{Binding Path=QuizAnswers}"
VerticalAlignment="Stretch"
Background="Transparent">
<ListBox.ItemTemplate>
<ItemContainerTemplate>
<RadioButton GroupName="{Binding Id}"
Content="{Binding Name}" />
</ItemContainerTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
this xaml code display only radiobutton question of quiz. How binding and display other Type of question (checkbox, combobox, textbox)? HELP)))
You can use an Item template selector in your items control to display different controls/templates depending on the question.
In the code below you can see that there's a conditional statement that checks the type of question and return a template based on the type of question.
public class QuiztemplateSelector : DataTemplateSelector
{
public DataTemplate TrueOrFalseTemplate { get; set; }
public DataTemplate MultiAnswerTemplate { get; set; }
public DataTemplate MultiChoiceTemplate { get; set; }
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
var question = item as QuizQuestion;
if (question.Type.Equals("TruOrFalse"))
return TrueOrFalseTemplate;
else if (question.Type.Equals("MultiAnswer"))
return MultiAnswerTemplate;
else if ("MultiChoice")
return MultiChoiceTemplate;
return null; //Or your default Template.
}
}
Now in your xaml, you can create templates for the types of questions (i.e. true/False, Multi choice, multi answer). Then you need to pass those templates to the QuiztemplateSelector as shown below.
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.Resources>
<DataTemplate x:Key="TrueOrFalse">
<!-- Write your True or false template here-->
</DataTemplate>
<DataTemplate x:Key="MultiChoice">
<!-- Write your MultiChoice template here-->
</DataTemplate>
<DataTemplate x:Key="MultiAnswer">
<!-- Write your multianswer Template here -->
</DataTemplate>
<local:QuizTemplateSelector x:Key="QuizTemplateSelector"
MultiAnswerTemplate="{StaticResource MultiAnswer}"
TrueOrFalseTemplate="{StaticResource TrueOrFalse}"
MultiChoiceTemplate="{StaticResource MultiChoice}" />
</Grid.Resources>
<ItemsControl ItemsSource="{Binding QuizCollection}" ItemTemplateSelector="{StaticResource QuiztemplateSelector}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ListBox ItemsSource="{Binding Path=QuizAnswers}"
VerticalAlignment="Stretch"
Background="Transparent">
<ListBox.ItemTemplate>
<ItemContainerTemplate>
<RadioButton GroupName="{Binding Id}"
Content="{Binding Name}" />
</ItemContainerTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
You can read more abour DataTemplateSelector here.
Wanting to get the color combobox (see image) behavior in my WPF ListView column.
Can someone help me get this started? I am comfortable with ListView binding but not sure how to implement this.
EDIT:
xmlns:System="clr-namespace:System;assembly=mscorlib"
<ObjectDataProvider MethodName="GetValues"
ObjectType="{x:Type System:Enum}"
x:Key="ColorList">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="Windows.Media.Color" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
Tells me type provided must be an enum.
Best Answer I have found:
How can I list colors in WPF with XAML?
ComboBox with ItemTemplate
You will have to use ItemTemplate for you ComboBox items:
<ComboBox ItemsSource="{Binding NamedColors}"
xmlns:converters="clr-namespace:TestLab.WPF">
<ComboBox.Resources>
<converters:ColorToSolidBrushConverter x:Key="ColorToBrush"/>
</ComboBox.Resources>
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Border BorderThickness="0" Height="20" Width="20"
Background="{Binding Value, Converter={StaticResource ColorToBrush}}"/>
<TextBlock Text="{Binding Key}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Brush converter
Also, you will need a color to brush converter, because binding doesn't do it automatically:
public class ColorToSolidBrushConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return new SolidColorBrush((Color)value);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Color name - color pair creation
And here is how you can create the color Name - color pairs (currently it is an instance method in the main Window class, but you can refactor it to some helper class):
private IEnumerable<KeyValuePair<String, Color>> GetColors()
{
return typeof(Colors)
.GetProperties()
.Where(prop =>
typeof(Color).IsAssignableFrom(prop.PropertyType))
.Select(prop =>
new KeyValuePair<String, Color>(prop.Name, (Color)prop.GetValue(null)));
}
Window code
And this is the window:
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.NamedColors = this.GetColors();
this.DataContext = this;
}
public IEnumerable<KeyValuePair<String, Color>> NamedColors
{
get;
private set;
}
}
ObjectDataProvider
Some code file:
public namespace SomeNamespace
{
public static class ColorHelper
{
public static IEnumerable<KeyValuePair<String, Color>> GetColors()
{
return typeof(Colors)
.GetProperties()
.Where(prop =>
typeof(Color).IsAssignableFrom(prop.PropertyType))
.Select(prop =>
new KeyValuePair<String, Color>(prop.Name, (Color)prop.GetValue(null)));
}
}
}
XAML object data provider:
...
xmlns:someNamespace="clr-namespace:SomeNamespace"
...
<ObjectDataProvider MethodName="GetColors"
ObjectType="{x:Type someNamespace.ColorHelper}"
x:Key="ColorList">
</ObjectDataProvider>
XAML comboBox:
<ComboBox ItemsSource="{Binding ColorList}" ...
Should be something like this :
<ComboBox ItemsSource="{Binding ItemSourceObs}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Rectangle Fill="{Binding Color}" Height="10" Width="10" />
<TextBlock Text="{Binding DisplayedText}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Where DisplayesText and Color (type of Brushes) are properties of an object lets say A and ItemSourceObs is ObservableCollection of type A
This approach is based on MVVM pattern
Using code behind working solution :
<ComboBox x:Name="ComboColor" Width="50" Height="50" >
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Rectangle Fill="{Binding Name}" Width="16" Height="16" Margin="0,2,5,2" />
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
And code behind :
public MainWindow()
{
InitializeComponent();
ComboColor.ItemsSource = typeof(Colors).GetProperties();
}
I am trying to create a TreeView with the following hierarchy:
Device1
--File1
--File2
--Hook1
--Hook2
Device2
--File1
--File1
Device3
--Hook1
So basically The Root level node is a device with children being File and Hook. I have created the following tree with the hierarchical data template
<TreeView ItemsSource="{Binding Devices}">
<HierarchicalDataTemplate DataType="{x:Type datamodel:Device}" ItemsSource="{Binding Files}">
<TextBlock Margin="5,5,0,0" Text="{Binding DeviceName}"/>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate DataType="{x:Type datamodel:File}">
<TextBlock Margin="5,0,0,0" Text="{Binding FileName}"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
Class Devices
{
public string DeviceName
{
get;set;
}
public string List<File> Files
{
get;set;
}
public string List<Hook> Hooks
{
get;set;
}
}
public class File
{
public string FileName
{
get;set;
}
public string FileType
{
get;set;
}
public string Location
{
get; set;
}
}
public class Hook
{
public string HookName
{
get;set;
}
public string Type
{
get;set;
}
}
I am able to add only one Datatemplate in the ItemTemplate of the HierarchicalDataTemplate. How do I specify two data types under a single HierarchicalDataTemplate??
You will have to use the converter here which will return the CompositeCollection which will contain both files and hooks. You will use this converter with ItemsSource of HierarchicalDataTemplate DataType="{x:Type datamodel:Device}"
Then you just need to define two datatemplates for File and Hook data type.
Thanks
I tried with the CompositeCollection but realized that the bound objects were converted to base types. So I used it in combination of DataTemplateSelector.
The following solution worked for me.
<TreeView ItemsSource="{Binding Devices}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type datamodel:Devices}" ItemTemplateSelector="{StaticResource LeafDataTemplateSelector}">
<HierarchicalDataTemplate.ItemsSource>
<MultiBinding Converter="{StaticResource CompositeCollectionConverter}">
<Binding Path="Files" />
<Binding Path="Hooks"/>
</MultiBinding>
</HierarchicalDataTemplate.ItemsSource>
<StackPanel Height="25" Orientation="Horizontal">
<Image Height="20" Width="20" Source="Device.png"/>
<TextBlock Margin="5,5,0,0" Text="{Binding Device}"/>
</StackPanel>
</HierarchicalDataTemplate>
<DataTemplate x:Key="FileKey" DataType="{x:Type datamodel:File}">
<StackPanel Height="25" Orientation="Horizontal" ToolTip="Installation File">
<Image Height="20" Width="20" Source="File.png" />
<TextBlock Text="{Binding FileName}"/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="HookKey" DataType="{x:Type datamodel:Hook}">
<StackPanel Height="25" Orientation="Horizontal" ToolTip="Installation Hook">
<Image Height="20" Width="20" Source="Hook.png" />
<TextBlock Margin="5,5,0,0" Text="{Binding HookName}"/>
</StackPanel>
</DataTemplate>
</TreeView.Resources>
The have used the template selector to select the appropriate template based on the key.
public class LeafDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate
SelectTemplate(object item, DependencyObject container)
{
FrameworkElement element = container as FrameworkElement;
if (element != null && item != null)
{
if (item is InstallationManifesFile)
return
element.FindResource("FileKey")
as DataTemplate;
else if (item is InstallationManifestHook)
return element.FindResource("HookKey")
as DataTemplate;
}
return null;
}
}
public class CompositeCollectionConverter : IMultiValueConverter
{
public object Convert(object[] values
, Type targetType
, object parameter
, System.Globalization.CultureInfo culture)
{
var res = new CompositeCollection();
foreach (var item in values)
if (item is IEnumerable && item != null)
res.Add(new CollectionContainer()
{
Collection = item as IEnumerable
});
return res;
}
public object[] ConvertBack(object value
, Type[] targetTypes
, object parameter
, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
If I have this structure:
public class Parent
{
public string Name{get; set;}
public List<Child> Childs {get; set;}
}
public class Child
{
public string Name{get; set;}
public string Value{get; set;}
public enum ValueType {get; set;}
}
public enum ValueType
{
Int,
Boolean,
String
};
public class ParentFactory
{
public List<Parent> Parents {get; set;}
public ParentFactory()
{
Child child1 = new Child() {Name="Filter1", Value="1", ValueType=ValueType.String};
Child child2 = new Child() {Name="isExecuted", Value="true", ValueType=ValueType.Boolean};
Child child3 = new Child() {Name="Width", Value="10", ValueType=ValueType.Int};
Parent parent1 = new Parent(){Name="Adam", Childs = new List<Child>(){child1, child2}};
Parent parent2 = new Parent(){Name="Kevin", Childs = new List<Child>(){child3}};
Parents = new List<Parent>(){parent1, parent2};
}
}
I want to bind the object: ParentFactory parentFactory = new ParentFactory() to ItemsControl:
<DockPanel>
<ItemsControl ItemsSource="{Binding Parents}">
</ItemsControl>
</DockPanel>
<Window.Resources>
<DataTemplate DataType="{x:Type Parent}">
<StackPanel Margin="2,2,2,1">
<Expander Header="{Binding Name}">
<ItemsControl ItemsSource="{Binding Childs}" />
</Expander>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type Child}">
<StackPanel>
<TextBox Grid.Column="0" Text="{Binding Name}" />
<TextBox Grid.Column="1" Text="{Binding Value}"/>
<TextBox Grid.Column="2" Text="{Binding ValueType}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
In the Stackpanel, there are one control: TextBox. However, I want it to be more dynamic: if the ValueType is a Boolean then use a Checkbox and else use a Textbox for the: <TextBox Grid.Column="1" Text="{Binding Value}"/>.
Is this possible, and if so, how can I achieve it?
You can change the DataTemplate dynamically
Xaml
<DataTemplate>
<DataTemplate.Resources>
<DataTemplate x:Key="Condition1">
<TextBox></TextBox> // Do you binding
</DataTemplate>
<DataTemplate x:Key="Condition2">
<CheckBox></CheckBox> // Do you binding
</DataTemplate>
</DataTemplate.Resources>
</DataTemplate>
<ContentPresenter x:Name="ContentField"
Content="{Binding}"
ContentTemplate="{StaticResource ResourceKey=Condition1}" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=ValueType}" Value="1">
<Setter TargetName="ContentField" Property="ContentTemplate" Value="{StaticResource ResourceKey=Condition2}" />
</DataTrigger>
</DataTemplate.Triggers>
Be sure to set the Bindings correctly ... and make the DataTemplates for Condition1 and Condition2
hope it helps :)
I think the easiest way to do it is to have both of them in your panel and define two new boolean properties that will control their Visibility through a Boolean To Visibility ValueConverter.
public bool IsValueTypeBoolean
{
get
{
...Conditions that will return true for CheckBox.
}
}
public bool IsValueTypeOther
{
get
{
return !this.IsValueBoolean;
}
}
<TextBox Grid.Column="2" Visibility="{Binding IsValueTypeOther, Converter={StaticResource visibilityConverter}}"/>
<CheckBox Grid.Column="2" Visibility="{Binding IsValueTypeBoolean, Converter={StaticResource visibilityConverter}}"/>
Boolean To VisibilityConverter
[ValueConversion(typeof(bool), typeof(Visibility))]
public class BooleanToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
bool myValue = (bool)value;
if (myValue)
return Visibility.Visible;
else
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Add this as a resource to your XAML:
<local:BooleanToVisibilityConverter x:Key="visibilityConverter"></local:VisibilityConverter>
I hope there are no typos...