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! :)
Related
I have am attempting to build a tree view where:
1. The TreeViewItems are generated by a list in my model.
2. Each TreeViewItem contains a ComboBox, and a dynamic element whose template I want to change based on the value selected in the ComboBox.
Here is my current xaml code.
<Window x:Class="MyTestWPF.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:MyTestWPF"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<local:NodeTypeToTemplateConverter x:Key="NodeTypeToTemplateConverter"/>
<DataTemplate x:Key="Template1">
<TextBlock Text="Template 1" />
</DataTemplate>
<DataTemplate x:Key="Template2">
<TextBlock Text="Template 2" />
</DataTemplate>
<Style x:Key="MyNodeTemplate" TargetType="ContentPresenter">
<Setter Property="ContentTemplate" Value="{StaticResource Template1}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=NodeType}">
<DataTrigger.Value>
<local:NodeTypesEnum>Type1</local:NodeTypesEnum>
</DataTrigger.Value>
<Setter Property="ContentTemplate" Value="{Binding Converter={StaticResource NodeTypeToTemplateConverter}}"/>
</DataTrigger>
</Style.Triggers>
</Style>
<HierarchicalDataTemplate DataType="{x:Type local:MyTreeNode}"
ItemsSource="{Binding Nodes}">
<StackPanel Orientation="Horizontal">
<ComboBox ItemsSource="{Binding Path=GetAvailableNodeType}"
SelectedItem="{Binding Path=NodeType}" />
<ContentPresenter Style="{StaticResource MyNodeTemplate}" Content="{Binding}" />
</StackPanel>
</HierarchicalDataTemplate>
</Window.Resources>
<TreeView x:Name="MyTree" ItemsSource="{Binding MyTreeModel}" />
</Window>
And its code-behind:
using System.Windows;
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new
{
MyTreeModel = new MyTreeNode[] {
new MyTreeNode() { Name = "1", Nodes = new MyTreeNode[] { new MyTreeNode() { Name= "2" } } }
}
};
}
}
The tree node type:
namespace MyTestWPF
{
public class MyTreeNode
{
public string Name { get; set; }
public NodeTypesEnum NodeType { get; set; }
public MyTreeNode[] Nodes { get; set; }
public NodeTypesEnum[] GetAvailableNodeType()
{
return new NodeTypesEnum[] { NodeTypesEnum.Type1, NodeTypesEnum.Type2 };
}
}
public enum NodeTypesEnum
{
Type1 = 0,
Type2 = 1
}
}
The Converter (NodeTypeToTemplateConverter) receives the whole ViewModel, and returns the name of the relevant template based on values in the model.
using System;
using System.Globalization;
using System.Windows.Data;
namespace MyTestWPF
{
public class NodeTypeToTemplateConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if((value as MyTreeNode).NodeType == NodeTypesEnum.Type1)
{
return "Template1";
} else
{
return "Template2";
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
The problem is that the above code causes a stack overflow exception. The first item in the TreeView endlessly calls NodeTypeToTemplateConverter's Convert method.
I figured it had to do with the DataTrigger.Value. Setting that to a value different from the default NodeType allows the page to load without overflow, but the moment any ComboBox is set to NodeType1, stack overflow.
I attempted to simply remove the DataTrigger.Value element, but that causes the Converter to never be called at all...
How can I dynamically build the template name based on the value selected by its neighboring ComboBox?
You probably want to use a DataTemplateSelector rather than a converter.
public class ComboBoxItemTemplateSelector : DataTemplateSelector
{
public DataTemplate Template1 { get; set; }
public DataTemplate Template2 { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
//Logic to select template based on 'item' value.
if (item == <template1Value>) return Template1; //TODO: replace <template1Value>
else if (item == <template2Value>) return Template2; //TODO: replace <template2Value>
else return new DataTemplate();
}
}
<local:ComboBoxItemTemplateSelector x:Key="ComboBoxItemTemplateSelector">
<local:ComboBoxItemTemplateSelector.Template1>
<DataTemplate>
<TextBlock Text="" />
</DataTemplate>
</local:ComboBoxItemTemplateSelector.Template1>
<local:ComboBoxItemTemplateSelector.Template2>
<DataTemplate>
<TextBlock Text="" />
</DataTemplate>
</local:ComboBoxItemTemplateSelector.Template2>
</local:ComboBoxItemTemplateSelector>
<ContentPresenter Content="{Binding NodeType}" ContentTemplateSelector="{StaticResource ComboBoxItemTemplateSelector}"/>
I have not fully tested this code, so let me know if you have any issues.
EDIT:
The template selector is only executed when the content changes so this won't work if you use {Binding}. A workaround for this would be to have the DataTemplate content bind to the parent's DataContext.
<DataTemplate>
<TextBlock Text="" DataContext="{Binding DataContext, RelativeSource={RelativeSource AncestorType=ContentPresenter}}"/>
</DataTemplate>
If this workaround is not acceptable, there are other ways to do this as well.
I am new to Silverlight and have been working with Simple MVVM to create a learning app. The Simple MVVM author provides several good examples of list/detail data binding but I'm looking an example of a screen to register a user. Since this is an internal app I can pass in the Windows ID from the hosting website and check it against the user database. If the user ID does not exist I would like to display a registration where the user can enter their first/last name and click a "register" button to save a new user record. Seems like a simple pattern but I haven't been able to figure it out.
Well I managed to combine a few different examples and come up with an example that works. This is part of a learning application that I am creating. It allows users (in my IT department) to suggest and vote for lunch destinations. For this app, the diner is the user. The code below will display a screen with the user's windows ID (read only) and inputs for first/last name in addition to a "register" button.
XAML (registerdinerview.xaml):
<navigation:Page x:Class="LTDestination.Views.RegisterDinerView"
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"
mc:Ignorable="d"
xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
d:DesignWidth="640"
d:DesignHeight="480"
Title="RegisterDinerView Page"
DataContext="{Binding Source={StaticResource Locator}, Path=RegisterDinerViewModel}"
>
<navigation:Page.Resources>
<Style TargetType="Button">
<Style.Setters>
<Setter Property="Height" Value="23"/>
<Setter Property="Width" Value="60"/>
<Setter Property="Margin" Value="5,0,0,0"/>
</Style.Setters>
</Style>
</navigation:Page.Resources>
<StackPanel x:Name="LayoutRoot">
<StackPanel x:Name="ContentStackPanel">
<TextBlock x:Name="HeaderText" Style="{StaticResource HeaderTextStyle}" Text="New User Registration"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<sdk:Label Content="User Id:" Grid.Row="0" />
<TextBlock Grid.Row="0" Grid.Column="1" Height="30" Text="{Binding Path=Model.UserId}" />
<sdk:Label Content="First Name:" Grid.Row="1" />
<TextBox Grid.Row="1" Grid.Column="1" Height="30" Text="{Binding Path=Model.FirstName, Mode=TwoWay}" />
<sdk:Label Content="Last Name:" Grid.Row="2" />
<TextBox Grid.Row="2" Grid.Column="1" Height="30" Text="{Binding Path=Model.LastName, Mode=TwoWay}" />
<Button x:Name="RegisterButton" Content="Register" Click="registerButton_Click"
Grid.Row="3" Grid.Column="1" Height="23" Width="75" HorizontalAlignment="Left" Margin="5,0,0,0"/>
</Grid>
</StackPanel>
</StackPanel>
View Code behind (registerdinerview.xaml.cs):
namespace LTDestination.Views
{
public partial class RegisterDinerView : Page
{
RegisterDinerViewModel _ViewModel;
public RegisterDinerView()
{
InitializeComponent();
_ViewModel = (RegisterDinerViewModel)DataContext;
_ViewModel.NewDiner();
}
private void registerButton_Click(object sender, RoutedEventArgs e)
{
_ViewModel.SaveChanges();
}
// Executes when the user navigates to this page.
protected override void OnNavigatedTo(NavigationEventArgs e)
{
}
}
}
ViewModel:
using System;
using System.Collections.Generic;
// Toolkit namespace
using LTDestination.Services;
using LTDestination.Web;
using SimpleMvvmToolkit;
namespace LTDestination.ViewModels
{
///
/// This class extends ViewModelDetailBase which implements IEditableDataObject.
///
/// Specify type being edited DetailType as the second type argument
/// and as a parameter to the seccond ctor.
///
///
/// Use the mvvmprop snippet to add bindable properties to this ViewModel.
///
///
public class RegisterDinerViewModel : ViewModelDetailBase
{
#region Initialization and Cleanup
private IDinerServiceAgent serviceAgent;
public RegisterDinerViewModel()
{
}
public RegisterDinerViewModel(IDinerServiceAgent serviceAgent)
{
this.serviceAgent = serviceAgent;
}
#endregion
#region Notifications
public event EventHandler<NotificationEventArgs<Exception>> ErrorNotice;
#endregion
#region Properties
private bool _IsBusy;
public bool IsBusy
{
get { return _IsBusy; }
set
{
_IsBusy = value;
NotifyPropertyChanged(m => m.IsBusy);
}
}
#endregion
#region Methods
public void NewDiner()
{
serviceAgent.GetDiners(DinersLoaded);
}
public void SaveChanges()
{
serviceAgent.AddDiner(base.Model);
serviceAgent.SaveChanges(DinerSaved);
IsBusy = true;
}
#endregion
#region Completion Callbacks
private void DinersLoaded(List<Diner> entities, Exception error)
{
// If no error is returned, set the model to entities
if (error == null)
{
base.Model = new Diner { UserId = App.UserId, Active = true };
}
// Otherwise notify view that there's an error
else
{
NotifyError("Unable to retrieve items", error);
}
// We're done
IsBusy = false;
}
private void DinerSaved(Exception error)
{
if (error != null)
{
NotifyError("Unable to save diner", error);
}
else
{
MessageBus.Default.Notify(MessageTokens.Navigation, this, new NotificationEventArgs(PageNames.Home));
}
}
#endregion
#region Helpers
// Helper method to notify View of an error
private void NotifyError(string message, Exception error)
{
// Notify view of an error
Notify(ErrorNotice, new NotificationEventArgs<Exception>(message, error));
}
#endregion
}
}
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);
}
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.
I have successfully applied the trick explained here. But I still have one problem.
Quick recap : I display users in a ListView. Users are regrouped by Country, and in the GroupStyle DataTemplate I display the sum of all group related Users.Total, using a Converter. But UI users can change the "Total" property value of Users through a modal window.
When there is only one item in the Group, both the User Total displayed and the sum are properly updated. But when there are multiple items in the group, only the User Total is updated (through binding) but the Converter that's supposed to make the sum (TotalSumConverter) is not even called!
Do you have any idea where it could come from? Should I use some kind of a trigger to make sure the Converter is called when there is a modification in the items?
The problem is that the value converter that calculates the sum for all the items in a group don't run when an item is changed, since there's no notification for changed items. One solution is to bind to something else that you can control how it does notifications and notify the group header when needed.
Below is a working example. You can change the count for a user in the text box and totals gets recalculated.
XAML:
<Window x:Class="UserTotalTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:userTotalTest="clr-namespace:UserTotalTest"
Title="Window1" Height="300" Width="300"
Name="this">
<Window.Resources>
<userTotalTest:SumConverter x:Key="SumConverter" />
<CollectionViewSource Source="{Binding Path=Users}" x:Key="cvs">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Country"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="10" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ListView
Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"
ItemsSource="{Binding Source={StaticResource cvs}}">
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Path=Name}" />
<GridViewColumn Header="Count" DisplayMemberBinding="{Binding Path=Count}" />
</GridView.Columns>
</GridView>
</ListView.View>
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<StackPanel Margin="10">
<TextBlock Text="{Binding Path=Name}" FontWeight="Bold" />
<ItemsPresenter />
<TextBlock FontWeight="Bold">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource SumConverter}">
<MultiBinding.Bindings>
<Binding Path="DataContext.Users" ElementName="this" />
<Binding Path="Name" />
</MultiBinding.Bindings>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
<ComboBox Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2"
ItemsSource="{Binding Path=Users}"
DisplayMemberPath="Name"
SelectedItem="{Binding Path=SelectedUser}" />
<TextBlock Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2" Text="{Binding Path=SelectedUser.Country}" />
<TextBox Grid.Row="4" Grid.Column="1" Text="{Binding Path=SelectedUser.Count}" />
</Grid>
</Window>
Code behind:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Data;
namespace UserTotalTest
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
DataContext = new UsersVM();
}
}
public class UsersVM : INotifyPropertyChanged
{
public UsersVM()
{
Users = new List<User>();
Countries = new string[] { "Sweden", "Norway", "Denmark" };
Random random = new Random();
for (int i = 0; i < 25; i++)
{
Users.Add(new User(string.Format("User{0}", i), Countries[random.Next(3)], random.Next(1000)));
}
foreach (User user in Users)
{
user.PropertyChanged += OnUserPropertyChanged;
}
SelectedUser = Users.First();
}
private void OnUserPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Count")
{
PropertyChanged(this, new PropertyChangedEventArgs("Users"));
}
}
public List<User> Users { get; private set; }
private User _selectedUser;
public User SelectedUser
{
get { return _selectedUser; }
set
{
_selectedUser = value; if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("SelectedUser"));
}
}
}
public string[] Countries { get; private set; }
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
public class User : INotifyPropertyChanged
{
public User(string name, string country, double total)
{
Name = name;
Country = country;
Count = total;
}
public string Name { get; private set; }
private string _country;
public string Country
{
get { return _country; }
set
{
_country = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Country"));
}
}
}
private double _count;
public double Count
{
get { return _count; }
set
{
_count = value; if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Count"));
}
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
public class SumConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
IEnumerable<User> users = values[0] as IEnumerable<User>;
string country = values[1] as string;
double sum = users.Cast<User>().Where(u =>u.Country == country).Sum(u => u.Count);
return "Count: " + sum;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
The trick you are using databinds the group footer to ListView.Items which will not update your view automatically, like a DependencyObject does for example. Instead force a refresh after each update to Total like this:
CollectionViewSource viewSource = FindResource("ViewSource") as CollectionViewSource;
viewSource.View.Refresh();
The problem with refreshing the view is that it completely refreshes it, and I have expanders for my grouping, that will expand back to their original state, even if the user closed them. So yes it's a possible workaround, but it's not completely satisfying.
Also your DependencyObject explanation is interesting, but then, why is it working when I have only one item in my group?