I want to bind an enum which has flags attribute to a listbox with a check list box item template in mvvm pattern? How can I do this?
[Flags]
public enum SportTypes
{
None = 0,
Baseball = 1,
Basketball = 2,
Football = 4,
Handball = 8,
Soccer = 16,
Volleyball = 32
}
<ListBox Name="checkboxList2"
ItemsSource="{Binding Sports}"
Margin="0,5"
SelectionMode="Multiple">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Path=SportTypeEnum, Converter={StaticResource enumBooleanConverter}}"
Content="{Binding Item}"/>
</DataTemplate>
</ListBox.ItemTemplate>
You can't easily bind the value directly, because the converter can't build the flag combination from a single flag. So you need to manage a collection of flags, and build the combination based on this collection. The difficulty is that the ListBox.SelectedItems property is readonly. However, this blog post gives a nice workaround, using an attached property.
Here's a complete example using this solution :
Code-behind
[Flags]
public enum MyEnum
{
Foo = 1,
Bar = 2,
Baz = 4
}
public partial class TestEnum : Window, INotifyPropertyChanged
{
public TestEnum()
{
InitializeComponent();
_flags = (MyEnum[])Enum.GetValues(typeof(MyEnum));
_selectedFlags = new ObservableCollection<MyEnum>();
_selectedFlags.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(_selectedFlags_CollectionChanged);
this.DataContext = this;
}
void _selectedFlags_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (_selectedFlags.Count == 0)
Value = default(MyEnum);
else
Value = _selectedFlags.Aggregate((v, acc) => acc | v);
}
private MyEnum[] _flags;
public MyEnum[] Flags
{
get { return _flags; }
set
{
_flags = value;
OnPropertyChanged("Flags");
}
}
private ObservableCollection<MyEnum> _selectedFlags;
public ObservableCollection<MyEnum> SelectedFlags
{
get { return _selectedFlags; }
set
{
_selectedFlags = value;
OnPropertyChanged("SelectedFlags");
}
}
private MyEnum _value;
public MyEnum Value
{
get { return _value; }
set
{
_value = value;
OnPropertyChanged("Value");
var currentFlags = _flags.Where(f => _value.HasFlag(f));
var addedFlags = currentFlags.Except(_selectedFlags).ToArray();
var removedFlags = _selectedFlags.Except(currentFlags).ToArray();
foreach (var f in addedFlags)
{
_selectedFlags.Add(f);
}
foreach (var f in removedFlags)
{
_selectedFlags.Remove(f);
}
}
}
private DelegateCommand<MyEnum> _setValueCommand;
public ICommand SetValueCommand
{
get
{
if (_setValueCommand == null)
{
_setValueCommand = new DelegateCommand<MyEnum>(v => Value = v);
}
return _setValueCommand;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
XAML
<Window x:Class="TestPad.TestEnum"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:TestPad"
Title="TestEnum" Height="300" Width="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Text="{Binding Value}" />
<ListBox Grid.Row="1"
ItemsSource="{Binding Flags}"
SelectionMode="Extended"
my:MultiSelectorBehavior.SynchronizedSelectedItems="{Binding SelectedFlags}">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding}" IsChecked="{Binding IsSelected, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ListBoxItem}}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ItemsControl Grid.Row="2"
ItemsSource="{Binding Flags}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Command="{Binding DataContext.SetValueCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"
CommandParameter="{Binding}"
Content="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
NOTE: for the sake of simplicity, the ViewModel is the Window class. In a real MVVM application, you would of course use a separate ViewModel class...
I see two solutions: One that is fully dynamic, and one that is static. The dynamic solution is a lot of work and IMO not trivial. The static one should be easy:
Create an Panel within your DataTemplate. There in, place for each Flag-Value a CheckBox. Then use the ConverterParameter to specifiy the flag, the converter should use. This would look something like this:
<StackPanel>
<CheckBox IsChecked="{Binding Path=SportTypeEnum, Converter={StaticResource enumBooleanConverter},ConverterParameter={x:Static local:SportTypes.Baseball}}" Content="Baseball"/>
<CheckBox IsChecked="{Binding Path=SportTypeEnum, Converter={StaticResource enumBooleanConverter},ConverterParameter={x:Static local:SportTypes.Basketball}}" Content="Basketball"/>
<CheckBox IsChecked="{Binding Path=SportTypeEnum, Converter={StaticResource enumBooleanConverter},ConverterParameter={x:Static local:SportTypes.Football}}" Content="Football"/>
<CheckBox IsChecked="{Binding ..../>
</StackPanel/>
In your converter you only have to do some logical AND-comparisons and you will have what you're looking for. If you're interested in the dynamic solution, make a comment, I can give you some ideas where to start. But IMO this will really not be trivial.
Additonal info
If you want to have a list instead of the StackPanel, use a ScrollViewer in a Border or even a ListBox.
To expand on Chris's post, here's a more thorough explanation of how you can do this.
This isn't the most ideal scenario, as the property holding the enum has to be a bit more complex than usual, but it works.
Converter code:
public class EnumFlagConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var theEnum = value as Enum;
return theEnum.HasFlag(parameter as Enum);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
var theEnum = parameter as Enum;
return theEnum;
}
}
XAML for converter:
<StackPanel>
<StackPanel.Resources>
<local:EnumFlagConverter x:Key="MyConverter" />
</StackPanel.Resources>
<CheckBox IsChecked="{Binding Path=SportTypeEnum, Converter={StaticResource MyConverter},ConverterParameter={x:Static local:SportTypes.Baseball}}" Content="Baseball"/>
<CheckBox IsChecked="{Binding Path=SportTypeEnum, Converter={StaticResource MyConverter},ConverterParameter={x:Static local:SportTypes.Basketball}}" Content="Basketball"/>
<CheckBox IsChecked="{Binding Path=SportTypeEnum, Converter={StaticResource MyConverter},ConverterParameter={x:Static local:SportTypes.Football}}" Content="Football"/>
<CheckBox IsChecked="{Binding ..../>
</StackPanel>
Then, to bind to this, I did a bit of trickery to get the converter to work properly:
private SportTypeEnum _TheSportType;
public SportTypeEnum _TheSportType
{
get { return _TheSportType; }
set
{
if (_TheSportType.HasFlag(value))
_TheSportType &= ~value;
else
_TheSportType |= value;
NotifyPropertyChanged();
}
}
Because of this special setter logic, you probably want to include a method like this to allow you to fully set the value from code:
private void ResetTheSportType()
{
_TheSportType = _TheSportType.None;
NotifyPropertyChanged(() => TheSportType);
}
Related
I want to change the properties of the dynamically populated UI components.
Here is my sample UI. The label and three textboxes created dynamically.
I want to change the visibility of the third textbox if the label content is R11.
and add a combo box with the static resource if the label is R12
Here is my sample code
my main screen XAML
<StackPanel>
<control:MethodControl></control:MethodControl>
<ContentControl Content="{Binding ChildViewModel}" />
</StackPanel>
MainViewModel
class MethodViewModel : ViewModelBase
{
#region Properties
private Method _method;
private PropertyViewModel _childViewModel;
#endregion
#region Getter & Setters
public PropertyViewModel ChildViewModel
{
get { return this._childViewModel; }
set
{
if (this._childViewModel != value)
{
this._childViewModel = value;
RaisePropertyChanged(() => ChildViewModel);
}
}
}
public Method Method
{
get { return _method; }
}
public ICommand UpdateCommand
{
get; private set;
}
#endregion
#region Constructor
/// <summary>
/// Initialize a new interface of the MEthodViewModel class
/// </summary>
public MethodViewModel()
{
//test
_method = new Method();
PropertyViewModel pwm = new PropertyViewModel();
pwm.CollectProperties(_method.Name, _method.Helper);
ChildViewModel = pwm;
UpdateCommand = new UpdateCommand(SaveChanges, () => string.IsNullOrEmpty(_method.Error));
}
#endregion
#region Functions
public void SaveChanges()
{
PropertyViewModel pwm = new PropertyViewModel();
pwm.CollectProperties(_method.Name, _method.Helper);
ChildViewModel = pwm;
}
Child View Model
class PropertyViewModel : ViewModelBase
{
private ObservableCollection<Property> _properties;
public ObservableCollection<Property> Properties
{
get { return _properties; }
}
public PropertyViewModel(string method, string reflection)
{
_properties = new ObservableCollection<Property>();
CollectProperties(method, reflection);
}
public void CollectProperties(string method, string reflection)
{
_properties.Clear();
int methodindex = Array.IndexOf((String[])Application.Current.Resources["MethodNames"], method);
switch (methodindex)
{
case 0:
foreach (String prop in (String[])Application.Current.Resources["Result1"])
{
PopulateProperty(prop, true);
}
break;
default:
foreach (String prop in (String[])Application.Current.Resources["Result2"])
{
PopulateProperty(prop, true);
}
break;
}
}
public PropertyViewModel()
{
_properties = new ObservableCollection<Property>();
}
private void PopulateProperty(string prop, bool p1)
{
Property temp = new Property(prop, "", 0, "");
_properties.Add(temp);
}
}
ChildViewModel XAML
<StackPanel >
<ItemsControl ItemsSource = "{Binding Properties}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation = "Horizontal">
<Label Content="{Binding Name}"></Label>
<TextBox Text = "{Binding Path, Mode=TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding StdDev, Mode=TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Unit, Mode=TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
and my resources
<x:Array x:Key="MethodNames" Type="sys:String"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<sys:String>MM1</sys:String>
<sys:String>MM2</sys:String>
<sys:String>MM3</sys:String>
</x:Array>
<x:Array x:Key="HelperMethods" Type="sys:String"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<sys:String>HM1</sys:String>
<sys:String>HM2</sys:String>
<sys:String>HM3</sys:String>
</x:Array>
<x:Array x:Key="Result1" Type="sys:String"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<sys:String>R11</sys:String>
<sys:String>R12</sys:String>
<sys:String>R13</sys:String>
</x:Array>
<x:Array x:Key="Result2" Type="sys:String"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<sys:String>R21</sys:String>
<sys:String>R22</sys:String>
<sys:String>R23</sys:String>
</x:Array>
<DataTemplate DataType="{x:Type modelViews:PropertyViewModel}">
<control:PropertyControl />
</DataTemplate>
part of the UI is changing with the selections of the combo boxes.
I need whole 3 text boxes for some options, only 1 for some and 1 textbox and 1 combo box for some options depends on the label name.
How can I add this property to dynamically populated user control?
You could add one or several triggers to the DataTemplate, e.g.:
<DataTemplate>
<StackPanel Orientation = "Horizontal">
<Label Content="{Binding Name}"></Label>
<TextBox Text = "{Binding Path, Mode=TwoWay}" Width = "100" Margin="3 5 3 5"/>
<TextBox Text="{Binding StdDev, Mode=TwoWay}" Width="100" Margin="3 5 3 5"/>
<TextBox x:Name="third" Text="{Binding Unit, Mode=TwoWay}" Width="100" Margin="3 5 3 5"/>
<ComboBox x:Name="combo" Visibility="Collapsed" />
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Name}" Value="R11">
<Setter TargetName="third" Property="Visibility" Value="Collapsed" />
<Setter TargetName="combo" Property="Visibility" Value="Visible" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
I find an easy solution to my question, so let me add it.
I add visibility in the properties and bind the textbox visibility to it.
here is the bool the visibility converter
public class BoolToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
return (bool) value ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
// Do the conversion from visibility to bool
}
}
Resource
<converter:BoolToVisibilityConverter x:Key="converter"></converter:BoolToVisibilityConverter>
text box
<TextBox Visibility="{Binding HasUnit,
Converter={StaticResource converter}}" />
I am trying to get access to a Boolean property value inside each row so I can use it to set a button visibility, however I am having trouble accessing this with a DataGridTemplateColumn. I was able to get the entire row object into a parameter that I pass to the button command, however I can't get just the UseSetting value to pass to the Visibility converter. I tried piggy backing off the text column as shown below, however the converters only seem to fire when the view is first loaded. Using breakpoints I can see that subsequent changes to the UseSetting property do not fire the converters. I do have NotifyOfPropertyChange setup correctly on the custom class used in the DataGrid.
What is the best way to gain access to a row property when using DataGridTemplateColumn? The reason why I am creating my own check boxes inside a DataGridTemplateColumn instead of using a CheckboxColumn is because the CheckboxColumn requires the row to be selected before it can be checked, and I need my checkbox to check upon a single click.
To be clear, there is no code behind for this view. Everything is in the view model, like the data grid's item source which is an ObservableCollection of the custom class "SharedSetting" that I included below.
<DataGrid MaxHeight="400" VerticalScrollBarVisibility="Auto" BorderThickness="1" CanUserAddRows="False" CanUserDeleteRows="False" BorderBrush="{DynamicResource AccentBaseColorBrush}" GridLinesVisibility="Horizontal" AutoGenerateColumns="False" ItemsSource="{Binding SharedSettings, NotifyOnSourceUpdated=True}">
<DataGrid.ColumnHeaderStyle>
<Style TargetType="{x:Type DataGridColumnHeader}" >
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="FontSize" Value="10" />
</Style>
</DataGrid.ColumnHeaderStyle>
<DataGrid.Columns>
<DataGridTextColumn Width="250" Header="Setting" Binding="{Binding Setting}" />
<DataGridTextColumn Width="300" Header="Value" ElementStyle="{StaticResource WrapText}" Binding="{Binding Value}" />
<DataGridTextColumn Width="75" Header="Use Setting" Binding="{Binding UseSetting, Mode=TwoWay}" x:Name="stackRowUseSetting" />
<DataGridTemplateColumn Width="50" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid Width="30" Height="30" x:Name="stackRow2">
<Button Background="Transparent" Foreground="{StaticResource AccentColorBrush}" ToolTip="Do Not Use Setting" Visibility="{Binding ElementName=stackRowUseSetting, Path=Binding, Converter={StaticResource TrueToVisibleConverter}}" BorderThickness="0" Margin="0,0,0,0" DataContext="{Binding ElementName=MainGrid, Path=DataContext}" Command="{Binding ToggleUseSettingCommand}" CommandParameter="{Binding ElementName=stackRow2,Path=DataContext}">
<iconPacks:PackIconMaterial Kind="CheckCircleOutline" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,0,0,0" />
</Button>
<Button Background="Transparent" Foreground="{StaticResource AccentColorBrush}" ToolTip="Use Setting" Visibility="{Binding ElementName=stackRowUseSetting, Path=Binding, Converter={StaticResource FalseToVisibleConverter}}" BorderThickness="0" Margin="0,0,0,0" DataContext="{Binding ElementName=MainGrid, Path=DataContext}" Command="{Binding ToggleUseSettingCommand}" CommandParameter="{Binding ElementName=stackRow2,Path=DataContext}">
<iconPacks:PackIconMaterial Kind="CheckboxBlankCircleOutline" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,0,0,0" />
</Button>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Also, the above XAML is just what I have right now. There are most likely some items here that are redundant and not needed. I have added and removed so many things trying to get this to work that it's a bit sloppy at the moment.
Here is the SharedSetting class with INotifyPropertyChanged
public class SharedSetting : INotifyPropertyChanged
{
private bool _useSetting;
private object _o;
private string _value;
private string _setting;
private string _group;
public SharedSetting(string groupName, string settingName, string settingValue, object value, bool use=false)
{
Group = groupName;
Setting = settingName;
Value = settingValue;
Object = value;
UseSetting = use;
}
public SharedSetting()
{
}
public string Group
{
get { return _group; }
set
{
_group = value;
NotifyPropertyChanged();
}
}
public string Setting
{
get { return _setting; }
set
{
_setting = value;
NotifyPropertyChanged();
}
}
public string Value
{
get { return _value; }
set
{
_value = value;
NotifyPropertyChanged();
}
}
public object Object
{
get { return _o; }
set
{
_o = value;
NotifyPropertyChanged();
}
}
public bool UseSetting
{
get { return _useSetting; }
set
{
_useSetting = value;
NotifyPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Here is one of the converters.
public sealed class TrueToVisibleConverter : MarkupExtension, IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var flag = false;
if (value is bool)
{
flag = (bool) value;
}
var visibility = (object) (Visibility) (flag ? 0 : 2);
return visibility;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is Visibility)
{
var visibility = (object) ((Visibility) value == Visibility.Visible);
return visibility;
}
return (object) false;
}
}
UPDATE 3/14/18
To address the first answer supplied below regarding removing my DataContext setting and using the properties just like all of the other columns, that does not work. That was the first thing I tried long long ago only to learn that DataGridTemplateColumn doesn't inherit the row's data context like the other columns do (the reason for my frustration in my below comment yesterday). I've included a screenshot showing the intellisense error stating that the property doesn't exist, when it is used the same way as the column above it.
You overright DataContext for your Button. DataContext="{Binding ElementName=MainGrid, Path=DataContext}" is wrong, so delete it and bind to the property as you do it in DataGridTextColumn. And for the binding of Command to the command which is not in SharedSetting use ElementName(as you have done it for DataContext) or RelativeSource.
Update:
Should work, but alternatively you can try
<Button Visibility="{Binding DataContext.UseSetting, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGridRow}, Converter={StaticResource TrueToVisibleConverter}}" />
I have have a problem with data binding. I have defined a application resource in XAML like this:
<Application.Resources>
<local:DataModel x:Key="myDataModel"/>
</Application.Resources>
I bound that model to a list, like this:
<ListView Name="ListBox"
ItemsSource="{Binding Source={StaticResource myDataModel}, Path=StatusList}"
HorizontalAlignment="Left"
HorizontalContentAlignment="Left"
VerticalContentAlignment="Top"
BorderThickness="0"
Background="#000000">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"></StackPanel>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<Button MinWidth="20"
MinHeight="100"
Background="{Binding Converter={StaticResource StatusConverter}}"
Content="{Binding}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
the problem now is that if I change the values of the application resource after binding, the list is not updated. It seems like the binding is done using a copy instead of a reference. The data of the model is updated fine, the PropertyChanged event is raised but the data inside the list never changes.
For your understanding: I have a network client who receives new data every 10 seconds that data needs to be drawn in that list. Right now whenever I receive data, I update the application resource, which as I said should be bound to the list. When I debug the code stopping right in front of the InitializeComponent() method of the XAML file containing the list and wait for a few seconds, I get the latest results of the data transferred, but thats it, it is never updated again.
Can you tell me a better way of defining a globally available instance of my model or a better way of binding it? As you see I need it in more than one part of my program.
public class DataModel
{
private IObservableCollection<short> this.statusList;
public IObservableCollection<short> StatusList
{
get {
return this.statusList;
}
set {
this.statusList = value;
this.RaisePropertyChanged("StatusList");
}
}
}
now you can do this one
this.StatusList = new ObservableCollection<short>();
hope this helps
EDIT
Here is an example that I am running without any problems.
<Window x:Class="WpfStackOverflowSpielWiese.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfStackOverflowSpielWiese"
Title="Window1"
Height="300"
Width="300">
<Window.Resources>
<local:DataModel x:Key="myDataModel" />
<local:StatusConverter x:Key="StatusConverter" />
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Button Grid.Row="0"
Content="Update"
Click="Button_Click" />
<ListView Name="ListBox"
Grid.Row="1"
ItemsSource="{Binding Source={StaticResource myDataModel}, Path=StatusList}"
HorizontalAlignment="Left"
HorizontalContentAlignment="Left"
VerticalContentAlignment="Top"
BorderThickness="0"
Background="#000000">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"></StackPanel>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<Button MinWidth="20"
MinHeight="100"
Background="{Binding Converter={StaticResource StatusConverter}}"
Content="{Binding}"></Button>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Window>
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media;
namespace WpfStackOverflowSpielWiese
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1() {
this.InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e) {
var dataModel = this.TryFindResource("myDataModel") as DataModel;
if (dataModel != null) {
dataModel.UpdateStatusList(new[] {(short)1, (short)2, (short)3});
}
}
}
public class DataModel : INotifyPropertyChanged
{
private ObservableCollection<short> statusList;
public DataModel() {
this.StatusList = new ObservableCollection<short>();
this.UpdateStatusList(new[] {(short)1, (short)2, (short)3});
}
public void UpdateStatusList(IEnumerable<short> itemsToUpdate) {
foreach (var s in itemsToUpdate) {
this.StatusList.Add(s);
}
}
public ObservableCollection<short> StatusList {
get { return this.statusList; }
set {
this.statusList = value;
this.RaisePropertyChanged("StatusList");
}
}
private void RaisePropertyChanged(string propertyName) {
var eh = this.PropertyChanged;
if (eh != null) {
eh(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class StatusConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
if (value is short) {
switch ((short)value) {
case 1:
return Brushes.Red;
case 2:
return Brushes.Orange;
case 3:
return Brushes.Green;
}
}
return Brushes.White;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
return DependencyProperty.UnsetValue;
}
}
}
I have solved the problem. I have altered the values inside the list. But what I needed to do was create a new one and add the new values. All I needed to do was add this line:
this.StatusList = new IObservableCollection<short>()
and instead of doing it like this:
for(int i=0; i<ListSize; i++)
StatusList[i] = i;
i had to do:
for(int i=0; i<ListSize; i++)
StatusList.add( i );
You also need a little workaround from here: ListBoxItem produces "System.Windows.Data Error: 4" binding error
The surronding element needs to set the alignment properties using styles like this:
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Top" />
</Style>
This way you can avoid the System.Windows.Data Error 4 exceptions
again thanks to everyone who answered! :)
I want to set the BorderThickness of a Border of a UserControl using 4 TextBoxes, but I can't get it to work.
XAML code demonstrating the problem (only this code in combination with the converter is needed):
<Window
x:Class="BorderThicknessBindingTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:BorderThicknessBindingTest="clr-namespace:BorderThicknessBindingTest"
Height="300" Width="500">
<Window.Resources>
<BorderThicknessBindingTest:ThicknessConverter x:Key="ThicknessConverter"/>
</Window.Resources>
<Grid Margin="10">
<Border
x:Name="MyBorder"
BorderBrush="Black"
Background="AliceBlue"
BorderThickness="3"/>
<TextBox
HorizontalAlignment="Center" VerticalAlignment="Center"
Text="{Binding Path=BorderThickness.Left, ElementName=MyBorder, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource ThicknessConverter}}"/>
</Grid>
</Window>
A converter is needed to parse the string input in the TextBox:
public class ThicknessConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value; // don't need to do anything here
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
double d;
Double.TryParse((string) value, out d); // Thickness.Left doesn't take a string
return d;
}
}
The TextBox correctly displays the Left part of the Thickness, but editing the TextBox does not result in a change in the way the left side of the Border is rendered. Oddly, the value that I set in the TextBox for Thickness.Left persists, so it seems that the value does get set, but the rendering isn't updated.
In the example code, changing the value in the TextBox, then resizing the Window, shows that the border on the left does take up additional space, but this space is blank.
Does anyone know how to go about and fixing this?
It's not dynamically updating the element on the screen because nothing has told the element that a field in its BorderThickness property has changed. You need to notify the element that its BorderThickness has changed, which you can only do by directly setting the dependency property to a new value - say, by making it the target of a binding to an object that does change notification.
It's something of a pain to make a view model for this, but once you do, it's done.
The window:
<Window x:Class="ThicknessDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ComponentModel="clr-namespace:System.ComponentModel;assembly=System" xmlns:ThicknessDemo="clr-namespace:ThicknessDemo" Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<ThicknessDemo:ThicknessViewModel x:Key="thickness" />
</Window.Resources>
<DockPanel DataContext="{StaticResource thickness}">
<Border DockPanel.Dock="Top"
Width="100"
Height="50"
Margin="5"
BorderBrush="Blue"
BorderThickness="{Binding Thickness}" />
<TextBox DockPanel.Dock="Top"
Text="{Binding Left, Mode=TwoWay}" />
<TextBox DockPanel.Dock="Top"
Text="{Binding Right, Mode=TwoWay}" />
<TextBox DockPanel.Dock="Top"
Text="{Binding Top, Mode=TwoWay}" />
<TextBox DockPanel.Dock="Top"
Text="{Binding Bottom, Mode=TwoWay}" />
<TextBlock DockPanel.Dock="Top" />
</DockPanel>
</Window>
The view model:
public class ThicknessViewModel : INotifyPropertyChanged
{
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler h = PropertyChanged;
if (h != null)
{
h(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public ThicknessViewModel()
{
_Thickness = new Thickness(1, 1, 1, 1);
}
private Thickness _Thickness;
public Thickness Thickness { get { return _Thickness; } set { _Thickness = value;} }
public double Left
{
get { return _Thickness.Left; }
set
{
_Thickness.Left = value;
OnPropertyChanged("Thickness");
}
}
public double Right
{
get { return _Thickness.Right; }
set
{
_Thickness.Right = value;
OnPropertyChanged("Thickness");
}
}
public double Top
{
get { return _Thickness.Top; }
set
{
_Thickness.Top = value;
OnPropertyChanged("Thickness");
}
}
public double Bottom
{
get { return _Thickness.Bottom; }
set
{
_Thickness.Bottom = value;
OnPropertyChanged("Thickness");
}
}
}
I believe this will point you in the right direction: Read halfway down it has 2 ways of approaching this, one with a converter and one without.
http://10rem.net/blog/2010/05/08/breaking-apart-the-margin-property-in-xaml-for-better-binding
The simplest solution for me turns out to be to just listen to the TextChanged event of the TextBox, and replace the BorderThickness in code behind.
MainWindow.xaml:
<Window
x:Class="BorderThicknessBindingTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:BorderThicknessBindingTest="clr-namespace:BorderThicknessBindingTest"
Height="300" Width="500">
<Grid Margin="10">
<Border
x:Name="MyBorder"
BorderBrush="Black"
Background="AliceBlue"
BorderThickness="3"/>
<TextBox
x:Name="MyTextBox"
HorizontalAlignment="Center" VerticalAlignment="Center"
Text="{Binding Path=BorderThickness.Left, ElementName=MyBorder, Mode=OneWay}"/>
</Grid>
</Window>
MainWindow.xaml.cs, in the constructor:
MyTextBox.TextChanged += (sender, e) =>
{
double d;
if (!double.TryParse(MyTextBox.Text, out d)) return;
var t = MyBorder.BorderThickness;
t.Left = d;
MyBorder.BorderThickness = t;
};
Right now this works for me, Robert Rossney's solution is better.
I have two HeaderedContentControls like those below that each have their content property bound to one of two view model properties of the same base type (one control is on the left side of the window and one on the right, thus the view model property names).
However, either view model property can be one of four different derived types. So the left could be an Airplane and the right can be a Car. Then later, the left could be a Boat and right could be an Airplane. I would like the Style property of the header controls to be dynamic based on the derived type. What's the best way to do this declaratively?
<Window...>
<StackPanel
Grid.Row="2"
Orientation="Horizontal" VerticalAlignment="Top">
<Border
Height="380"
Width="330"
Margin="0,0,4,0"
Style="{StaticResource MainBorderStyle}">
<HeaderedContentControl
Content="{Binding Path=LeftChild}"
Header="{Binding LeftChild.DisplayName}"
Style="{StaticResource StandardHeaderStyle}"
/>
</Border>
<Border
Height="380"
Width="330"
Style="{StaticResource MainBorderStyle}">
<HeaderedContentControl
Content="{Binding Path=RightChild}"
Header="{Binding RightChild.DisplayName}"
Style="{StaticResource StandardHeaderStyle}"
/>
</Border>
</StackPanel>
</Window>
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:myViewModelNamespace;assembly=myViewModelAssembly"
xmlns:vw="clr-namespace:myViewNamespace" >
<!--***** Item Data Templates ****-->
<DataTemplate DataType="{x:Type vm:CarViewModel}">
<vw:CarView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:BoatViewModel}">
<vw:BoatView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:AirplaneViewModel}">
<vw:AirplaneView />
</DataTemplate>
<!--*****
Other stuff including the StandardHeaderStyle and the MainBorderStyle
****-->
</ResourceDictionary>
Are you sure you need to vary HeaderedContentControl's Style, not the ContentTemplate basing on Content's dynamic type? In other words: do you need to vary the control's style or you just need to vary the item's data-template?
Because there is very handy property ContentTemplateSelector and if you'll write very simple class you'll be able to select the DataTemplate basing on content's dynamic type.
If that's not the case and you are sure you need to vary the Style, then could you please elaborate a little which parts of the style you'd like to vary - maybe there a workaround through the same ContentTemplateSelector is available.
In case you insist on varying the styles, think a little about using data trigger inside your style - using a very simple converter you'll be able to vary certain properties (or all of them if you prefer) of your style.
I'll be glad to provide you further assistance as soon as you'll elaborate the specifics of your problem.
UPD: OK, author insists that he need to vary the Style. Here are two possible ways of how you can do that.
First and simple solution, but severely limited one: since your Header content can be specified through Content content you can do this:
<DataTemplate x:Key="DefaultTemplate">
<HeaderedContentControl Content="{Binding}"
Header="{Binding DisplayName}"
Style="{StaticResource DefaultStyle}" />
</DataTemplate>
<DataTemplate x:Key="CarTemplate"
DataType="dm:Car">
<HeaderedContentControl Content="{Binding}"
Header="{Binding DisplayName}"
Style="{StaticResource CarStyle}" />
</DataTemplate>
<DataTemplate x:Key="BoatTemplate"
DataType="dm:Boat">
<HeaderedContentControl Content="{Binding}"
Header="{Binding DisplayName}"
Style="{StaticResource BoatStyle}" />
</DataTemplate>
<u:TypeBasedDataTemplateSelector x:Key="MySelector"
DefaultTemplate="{StaticResource DefaultTemplate}"
NullTemplate="{StaticResource DefaultTemplate}">
<u:TypeMapping Type="dm:Car" Template="{StaticResource CarTemplate}" />
<u:TypeMapping Type="dm:Boat" Template="{StaticResource BoatTemplate}" />
</u:TypeBasedDataTemplateSelector>
<ContentPresenter Content="{Binding LeftChild}"
ContentTemplateSelector="{StaticResource MySelector}" />
The only code you'll need to back this purely declarative solution is a very simple template selector implementation. Here it goes:
public class TypeMapping
{
public Type Type { get; set; }
public DataTemplate Template { get; set; }
}
public class TypeBasedDataTemplateSelector : DataTemplateSelector, IAddChild
{
public DataTemplate DefaultTemplate { get; set; }
public DataTemplate NullTemplate { get; set; }
private readonly Dictionary<Type, DataTemplate> Mapping = new Dictionary<Type, DataTemplate>();
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item == null)
return NullTemplate;
DataTemplate template;
if (!Mapping.TryGetValue(item.GetType(), out template))
template = DefaultTemplate;
return template;
}
#region IAddChild Members
public void AddChild(object value)
{
if (!(value is TypeMapping))
throw new Exception("...");
var tm = (TypeMapping)value;
Mapping.Add(tm.Type, tm.Template);
}
public void AddText(string text)
{
throw new NotImplementedException();
}
#endregion
}
The second solution is more generic and can be applied to the cases where Header content has nothing to do with Content content. It bases on the Binding's converter capabilities.
<Style x:Key="StandardHeaderedStyle">
<!--...-->
</Style>
<Style x:Key="CarHeaderedStyle"
BasedOn="{StaticResource StandardHeaderedStyle}">
<!--...-->
</Style>
<Style x:Key="BoatHeaderedStyle"
BasedOn="{StaticResource StandardHeaderedStyle}">
<!--...-->
</Style>
<Style x:Key="UnknownHeaderedStyle"
BasedOn="{StaticResource StandardHeaderedStyle}">
<!--...-->
</Style>
<u:StylesMap x:Key="MyStylesMap"
FallbackStyle="{StaticResource UnknownHeaderedStyle}">
<u:StyleMapping Type="Car" Style="{StaticResource CarHeaderedStyle}" />
<u:StyleMapping Type="Boat" Style="{StaticResource BoatHeaderedStyle}" />
</u:StylesMap>
<u:StyleSelectorConverter x:Key="StyleSelectorConverter" />
<HeaderedContentControl Content="{Binding LeftChild}"
Header="{Binding LeftChild.DisplayName}">
<HeaderedContentControl.Style>
<Binding Path="LeftChild"
Converter="{StaticResource StyleSelectorConverter}"
ConverterParameter="{StaticResource MyStylesMap}" />
</HeaderedContentControl.Style>
</HeaderedContentControl>
It also requires some of backing code:
public class StyleMapping
{
public Type Type { get; set; }
public Style Style { get; set; }
}
public class StylesMap : Dictionary<Type, Style>, IAddChild
{
public Style FallbackStyle { get; set; }
#region IAddChild Members
public void AddChild(object value)
{
if (!(value is StyleMapping))
throw new InvalidOperationException("...");
var m = (StyleMapping)value;
this.Add(m.Type, m.Style);
}
public void AddText(string text)
{
throw new NotImplementedException();
}
#endregion
}
public class StyleSelectorConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var m = (StylesMap)parameter;
if (value == null)
return m.FallbackStyle;
Style style;
if (!m.TryGetValue(value.GetType(), out style))
style = m.FallbackStyle;
return style;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
HTH
My answer is an elaboration on Archimed's. Don't hesitate to ask further!
<Window x:Class="Datatemplate_selector.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300"
xmlns:local="clr-namespace:Datatemplate_selector">
<Window.Resources>
<DataTemplate DataType="{x:Type local:CarDetail}">
<Border BorderBrush="Yellow" BorderThickness="2">
<HeaderedContentControl Margin="4" Foreground="Red">
<HeaderedContentControl.Header>
<Border BorderBrush="Aquamarine" BorderThickness="3">
<TextBlock Text="{Binding Name}"/>
</Border>
</HeaderedContentControl.Header>
<HeaderedContentControl.Content>
<Border BorderBrush="CadetBlue" BorderThickness="1">
<TextBlock TextWrapping="Wrap" Text="{Binding Description}"/>
</Border>
</HeaderedContentControl.Content>
</HeaderedContentControl>
</Border>
</DataTemplate>
<DataTemplate DataType="{x:Type local:HouseDetail}">
<HeaderedContentControl Margin="4" Foreground="Yellow" FontSize="20"
Header="{Binding Name}">
<HeaderedContentControl.Content>
<TextBlock Foreground="BurlyWood" TextWrapping="Wrap"
Text="{Binding Description}"/>
</HeaderedContentControl.Content>
</HeaderedContentControl>
</DataTemplate>
<DataTemplate DataType="{x:Type local:ItemDetail}">
<HeaderedContentControl Margin="4" Foreground="Green" FontStyle="Italic"
Content="{Binding Description}"
Header="{Binding Name}">
</HeaderedContentControl>
</DataTemplate>
</Window.Resources>
<StackPanel>
<ItemsControl ItemsSource="{Binding ItemDetails}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="2"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</StackPanel>
using System.Collections.ObjectModel;
using System.Windows;
namespace Datatemplate_selector
{
public partial class Window1 : Window
{
public ObservableCollection<ItemDetail> ItemDetails { get; set; }
public Window1()
{
ItemDetails = new ObservableCollection<ItemDetail>
{
new CarDetail{Name="Trabant"},
new HouseDetail{Name="Taj Mahal"}
};
DataContext = this;
InitializeComponent();
}
}
public class ItemDetail:DependencyObject
{
public string Name
{
get { return (string)GetValue(NameProperty); }
set { SetValue(NameProperty, value); }
}
public static readonly DependencyProperty NameProperty =
DependencyProperty.Register("Name",
typeof(string),
typeof(ItemDetail),
new UIPropertyMetadata(string.Empty));
public virtual string Description
{
get { return Name + " has a lot of details"; }
}
}
public class CarDetail:ItemDetail
{
public override string Description
{
get { return string.Format("The car {0} has two doors and a max speed of 90 kms/hr", Name); }
}
}
public class HouseDetail:ItemDetail
{
public override string Description
{
get { return string.Format("The house {0} has two doors and a backyard", Name); }
}
}
}
PS: I thought that this use of inheritance in a generic collection was not supported in .Net 3. I am pleasurably surprised that this code works!
try using the Style Selector class:
http://msdn.microsoft.com/en-us/library/system.windows.controls.styleselector.aspx
I haven't used it myself specifically, so i don't have any sample code for you to check out, but the MSDN link has some.