Reputation: 307
I need to bind a List to a UniformGrid into a WP Window using WPF MVVM.
I had in mind to do something like this:
Into my VM:
private List<Rat> rats;
private UniformGrid uniformGrid;
public List<Rat> Rats
{
get { return rats; }
set
{
if (rats != value)
{
//update local list value
rats = value;
//create View UniformGrid
if (uniformGrid == null)
uniformGrid = new UniformGrid() { Rows=10};
else
uniformGrid.Children.Clear();
foreach(Rat rat in value)
{
StackPanel stackPanel = new StackPanel();
Ellipse ellipse = new Ellipse(){Height=20, Width=20, Stroke= Brushes.Black};
if (rat.Sex== SexEnum.Female)
ellipse.Fill= Brushes.Pink;
else
ellipse.Fill= Brushes.Blue;
stackPanel.Children.Add(ellipse );
TextBlock textBlock = new TextBlock();
textBlock.Text= rat.Name + " (" + rat.Age +")";
stackPanel.Children.Add( textBlock );
uniformGrid.Children.Add(stackPanel);
}
OnPropertyChanged("Rats");
}
}
}
The VM is correctly informed when the list needs to be refreshed into the view via an event. So at this point I would need my View to be correctly bound to the VM. I made it this way:
<GroupBox x:Name="GB_Rats" Content="{Binding Rats}" Header="Rats" HorizontalAlignment="Left" Height="194" Margin="29,10,0,0" VerticalAlignment="Top" Width="303">
Is this the correct global approch?
Concretely, when attempting to run the code, this line fails to execute:
uniformGrid = new UniformGrid() { Rows=10};
->
An unhandled exception of type 'System.InvalidOperationException' occurred in PresentationCore.dll
Additional information: The calling thread must be STA, because many UI components require this.
This lets me think that I should not proceed this way from a MVVM point of view.
Thx for your kind assistance.
Upvotes: 0
Views: 6368
Reputation: 1516
The ViewModel
isn't supposed to instantiate any UI controls, this should be the View's responsibility.
So in your code you shouldn't try to create StackPanels
, Ellipses
etc.
Also try to use the types that already have Change notification - for instead of
List<T>
use ObservableCollection<T>
MSDN, i wouldn't recommend replacing a whole list when its value change.
The right way to do this in the MVVM pattern is to create a DataTemplate
for the Rat
like this:
ViewModel:
public class MainWindowViewModel
{
public ObservableCollection<Rat> Rats { get; set; } =
new ObservableCollection<Rat>()
{
new Rat()
{
Name = "Fred",
Age = "19",
Sex = SexEnum.Male
},
new Rat()
{
Name = "Martha",
Age = "21",
Sex = SexEnum.Female
}
};
}
Model - Rat and Sex:
public class Rat
{
public SexEnum Sex { get; set; }
public string Name { get; set; }
public string Age { get; set; }
}
public enum SexEnum
{
Female,
Male
}
As you want to present the Models value of Sex in one of two colors you should use a IValueConverter
for that:
[ValueConversion(typeof(SexEnum), typeof(Brush))]
public class SexToColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is SexEnum))
throw new ArgumentException("value not of type StateValue");
SexEnum sv = (SexEnum)value;
//sanity checks
if (sv == SexEnum.Female)
return Brushes.Red;
return Brushes.Blue;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
This is then used in your window:
Window:
<Window x:Class="WpfApplication1.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:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525"
xmlns:ViewModel="WpfApplication1.VM"
xmlns:Converters ="clr-namespace:WpfApplication1.Converters"
>
<Grid>
<Grid.Resources>
<Converters:SexToColorConverter x:Key="SexToBrushConverter"></Converters:SexToColorConverter>
</Grid.Resources>
<ComboBox x:Name="comboBox" ItemsSource="{Binding Rats}" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="120">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Ellipse Width="10" Height="10" Fill="{Binding Sex, Converter={StaticResource SexToBrushConverter}}"></Ellipse>
<TextBlock Margin="5" Text="{Binding Name}"></TextBlock>
<TextBlock Margin="5" Text="{Binding Age}"></TextBlock>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</Window>
Note the DataTemplate
that is assigned to the ComboBox.ItemTemplate
property and the declaration of the Converters:SexToColorConverter
and its usage to change the color of the ellipse in the Fill
binding.
Update 4.4.2016 16:30
Window using a GroupBox
with CheckBoxes
to display the list
<Window x:Class="WpfApplication1.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:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525"
xmlns:Converters ="clr-namespace:WpfApplication1.Converters"
xmlns:vm="clr-namespace:WpfApplication1.VM">
<Window.DataContext>
<vm:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<Grid.Resources>
<Converters:SexToColorConverter x:Key="SexToBrushConverter"></Converters:SexToColorConverter>
</Grid.Resources>
<GroupBox>
<ItemsControl ItemsSource="{Binding Rats}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Margin="5"></CheckBox>
<Ellipse Width="10" Height="10" Fill="{Binding Sex, Converter={StaticResource SexToBrushConverter}}"></Ellipse>
<TextBlock Margin="5" Text="{Binding Name}"></TextBlock>
<TextBlock Margin="5" Text="{Binding Age}"></TextBlock>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</GroupBox>
</Grid>
</Window>
I guess the target is to also select the Rats, depending on how MVVM purist you want to be you'd add a List of RatViewModels
, that have a bool IsChecked property and bind the ItemsSource
to a ObservableCollection<RatViewModel>
and synchronize this list with your Models List<Rat>
Upvotes: 4