How to change the itemsSource of a ComboBox in C# - c#

I am trying to change the itemsSource of a comboBox at run-time. In this question I was told to do, comboBox.itemssource.... That would be okay if all I needed to do was create a new comboBox and then call the command on it. However, I need to perform this operation on a comboBox that already exists in my User Control through xaml. In that case, how would I reference it? I know how to bind to properties in the control, but in this case I would need to get the whole control. Am I over-thinking it? What is the best way to do what I'm thinking?
This how I am currently switching the Collections in the comboBox (This is all at the model level):
//Property for Combo Box List
public ObservableCollection<string> ComboBoxList
{
get { return _comboBoxList; }
set
{
if (Equals(value, _comboBoxList)) return;
_comboBoxList = value;
OnPropertyChanged("ComboBoxList");
}
}
public string SelectedCommand
{
get { return _selectedCommand; }
set
{
_selectedCommand = value;
NotifyPropertyChange(() => SelectedCommand);
if (SelectedCommand == "String Value")
{
ComboBoxList = new ObservableCollection<string>(newList);
}
}
}
The collections switch when using this implementation, but the selectedItem in the comboBox doesn't stick. For example, when I click on a different command and then switch back, the box no longer has a selectedItem.
UPDATE
I have a property called selectedOperation that is bound to my comboBox. It contains a simple getter and setter, with a NotifyPropertyChange. This makes it so that the selectedItem in the box stays selected. BUT, if the user clicks on a different command and selects a different item in the comboBox, that new item takes it's place. I need to be able to have a selectedItem for each collection that the comboBox holds.
For example:
Let's say there are 2 commands in the listBox, A and B. Each create a different collection in the comboBox. A creates a collection of numbers, and B creates a collection of names.
For command A the user selects 5. When A is selected the comboBox should display 5 as it's selectedItem. A -> 5
For command B the user selectes Roger. When B is selected the comboBox should display "Roger" as it's selectedItem. B -> Roger
Currently, the comboBox does not remember it's selectedItem when the user switches between commands.

I would rather use a DataContext and update that source than manually updating a ComboBox.ItemsSourceproperty.
This way there would be no need to know about the controls at all.
Here is a small example :
When the user clicks the button, you just take care of updating your data, not the controls presenting it.
<Window x:Class="WpfApplication10.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" x:Name="Window1">
<Grid DataContext="{Binding ElementName=Window1}">
<StackPanel>
<Button Click="Button_Click">Some data 1</Button>
<Button Click="Button_Click_1">Some data 2</Button>
<ListBox x:Name="ComboBox1" ItemsSource="{Binding Collection}"></ListBox>
</StackPanel>
</Grid>
</Window>
using System.Collections.ObjectModel;
using System.Windows;
namespace WpfApplication10
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private readonly ObservableCollection<string> _collection = new ObservableCollection<string>();
public MainWindow()
{
InitializeComponent();
}
public ObservableCollection<string> Collection
{
get { return _collection; }
}
private void Button_Click(object sender, RoutedEventArgs e)
{
_collection.Clear();
for (int i = 0; i < 5; i++)
{
_collection.Add("method 1 item " + i);
}
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{ _collection.Clear();
for (int i = 0; i < 5; i++)
{
_collection.Add("method 2 item " + i);
}
}
}
}
Update
If you want to use a new collection instead of removing items, you will have to implement INotifyPropertyChanged for the collection.
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
namespace WpfApplication10
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window, INotifyPropertyChanged
{
private ObservableCollection<string> _collection = new ObservableCollection<string>();
public MainWindow()
{
InitializeComponent();
}
public ObservableCollection<string> Collection
{
get { return _collection; }
set
{
if (Equals(value, _collection)) return;
_collection = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void Button_Click(object sender, RoutedEventArgs e)
{
Collection = new ObservableCollection<string>(new[] {"1", "2"});
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
Collection = new ObservableCollection<string>(new[] {"3", "4"});
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Note: the [CallerMemberName] saves you from adding the property name each time you invoke the invocator but it's only for .NET 4.5 if I remember correctly.
If you are not under .NET 4.5 then you'll have to put OnPropertyChanged("Collection") instead.
Reference : INotifyPropertyChanged
Also, update Collection with a new collection, not _collection otherwise your UI won't be notified.
EDIT 2
You need to track the selected item according the collection used.
<Window x:Class="WpfApplication10.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" x:Name="Window1">
<Grid>
<StackPanel>
<Button Click="Button_Click">Some data 1</Button>
<Button Click="Button_Click_1">Some data 2</Button>
<ListBox x:Name="ComboBox1" ItemsSource="{Binding}" SelectedItem="{Binding MySelectedItem}" />
</StackPanel>
</Grid>
</Window>
Code behind :
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
namespace WpfApplication10
{
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
MyCustomCollection1 = new MyCustomCollection<string>(new[] {"a", "b"});
MyCustomCollection2 = new MyCustomCollection<string>(new[] {"c", "d"});
}
public MyCustomCollection<string> MyCustomCollection1 { get; set; }
public MyCustomCollection<string> MyCustomCollection2 { get; set; }
private void Button_Click(object sender, RoutedEventArgs e)
{
DataContext = MyCustomCollection1;
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
DataContext = MyCustomCollection2;
}
}
public class MyCustomCollection<T> : ObservableCollection<T>
{
private T _mySelectedItem;
public MyCustomCollection(IEnumerable<T> collection) : base(collection)
{
}
public T MySelectedItem
{
get { return _mySelectedItem; }
set
{
if (Equals(value, _mySelectedItem))return;
_mySelectedItem = value;
OnPropertyChanged(new PropertyChangedEventArgs("MySelectedItem"));
}
}
}
}

try changing the collection via style using some trigger (can be any trigger data/event) here is an example:
<Style x:Key="MySelectItemSourceStyle" TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding Collection1}" />
<Style.Triggers>
<DataTrigger Binding="{Binding SomeValue}" Value="SecondCollection">
<Setter Property="ItemsSource" Value="{Binding Collection2}" />
</DataTrigger>
</Style.Triggers>
</Style>

Related

IEnumerable changed event

I am programming an app in WinUI 3 using C#.
There are some controls that have an ItemsSource property.
When I pass an IEnumerable into the ItemsSource and then change the IEnumerable, the control changes accordingly.
I now want to implement the same behavior in my custom UserControl.
I have the following code, but it only works with IEnumerables that implement INotifyCollectionChanged (eg. ObservableCollection).
WinUI supports IEnumerables that don't implement INotifyCollectionChanged.
How can I do the same?
Here is my code:
public sealed partial class MyUserControl : UserControl
{
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(MyUserControl), new PropertyMetadata(null));
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
private IEnumerable _oldItemsSource;
public MyUserControl()
{
this.InitializeComponent();
RegisterPropertyChangedCallback(ItemsSourceProperty, OnItemsSourceChanged);
}
private void OnItemsSourceChanged(DependencyObject sender, DependencyProperty prop)
{
if (prop == ItemsSourceProperty)
{
var newValue = (IEnumerable)sender.GetValue(ItemsSourceProperty);
if (_oldItemsSource is INotifyCollectionChanged oldCollection)
{
oldCollection.CollectionChanged -= OnCollectionChanged;
}
if (newValue is INotifyCollectionChanged collection)
{
collection.CollectionChanged += OnCollectionChanged;
}
_oldItemsSource = ItemsSource;
}
}
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// Update the control here
}
}
WinUI 3 allows me to use a List (doesn't implement INotifyCollectionChanged) as an ItemsSource.
Changes made to the List affect the control.
This is the code inside a test page:
public TestPage()
{
this.InitializeComponent();
var list = new List<string> { "Item1", "Item2", "Item3" };
var bar = new BreadcrumbBar(); ;
bar.ItemsSource = list;
this.Content = bar;
list.Add("Item4");
// The BreadcrumbBar now has 4 elements.
}
When I pass an IEnumerable into the ItemsSource and then change the IEnumerable, the control changes accordingly.
No, it doesn't unless the IEnumerable is an INotifyCollectionChanged.
Try to call list.Add("Item4") in an event handler after the control has been initially rendered if you don't believe me.
For example, this code will not add "Item4" to the BreadcrumbBar control:
public sealed partial class TestPage : Page
{
private readonly List<string> list = new List<string> { "Item1", "Item2", "Item3" };
public TestPage()
{
this.InitializeComponent();
breadcrumbBar.ItemsSource = list;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
list.Add("Item4");
}
}
Changing the type of list to ObservableCollection<string> will make it work as expected.
So your custom control is no worse than any built-in control in that sense. The source collection must notify the view somehow.
WinUI 3 allows me to use a List (doesn't implement INotifyCollectionChanged) as an ItemsSource. Changes made to the List affect the control.
It most certainly doesn't react to changes of "dumb" collections. Your example only works because the items haven't been constructed yet, and they won't be until the control is loaded (your code is all in the constructor).
This example will show you a comparison between a plain List and an ObservableCollection.
Let's say we have a UserControl like this one.
TestUserControl.xaml
<UserControl
x:Class="ItemsSourceTest.TestUserControl"
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:local="using:ItemsSourceTest"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid>
<ListView ItemsSource="{x:Bind ItemsSource, Mode=OneWay}" />
</Grid>
</UserControl>
TestUserControl.xaml.cs
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Data;
using System.Collections;
using Windows.Foundation.Collections;
namespace ItemsSourceTest;
public sealed partial class TestUserControl : UserControl
{
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register(
nameof(ItemsSource),
typeof(IEnumerable),
typeof(TestUserControl),
new PropertyMetadata(default, (d, e) => (d as TestUserControl)?.UpdateItemsSource()));
public TestUserControl()
{
this.InitializeComponent();
}
// You don't need CollectionViewSource or CollectionView
// in order to populate the ListView. This is just to show
// you how you can get events when the collection changes.
public CollectionViewSource? CollectionViewSource { get; set; }
public ICollectionView? CollectionView { get; set; }
public IEnumerable ItemsSource
{
get => (IEnumerable)GetValue(ItemsSourceProperty);
set => SetValue(ItemsSourceProperty, value);
}
private void UpdateItemsSource()
{
CollectionViewSource = new CollectionViewSource()
{
Source = ItemsSource,
};
if (CollectionView is not null)
{
CollectionView.VectorChanged -= CollectionView_VectorChanged;
}
CollectionView = CollectionViewSource.View;
CollectionView.VectorChanged += CollectionView_VectorChanged;
}
private void CollectionView_VectorChanged(IObservableVector<object> sender, IVectorChangedEventArgs #event)
{
// Your can do your work for collection changes here...
}
}
And a MainPage a ListView and a TestUserControl both bound to a plain List named NonObservableItems and another set of a ListView and a TestUserControl both bound to an ObservableCollection named ObservableItems.
When you add items to the collections, you'll see that only the controls bound to the ObservableItems will be populated.
MainPage.xaml
<Page
x:Class="ItemsSourceTest.MainPage"
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:local="using:ItemsSourceTest"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
mc:Ignorable="d">
<Grid RowDefinitions="Auto,*">
<Button
Grid.Row="0"
Click="Button_Click"
Content="Add item" />
<Grid
Grid.Row="1"
ColumnDefinitions="*,*"
RowDefinitions="Auto,*,*">
<TextBlock
Grid.Row="0"
Grid.Column="0"
Text="Non-Observable Items" />
<ListView
Grid.Row="1"
Grid.Column="0"
ItemsSource="{x:Bind NonObservableItems, Mode=OneWay}" />
<local:TestUserControl
Grid.Row="2"
Grid.Column="0"
ItemsSource="{x:Bind NonObservableItems, Mode=OneWay}" />
<TextBlock
Grid.Row="0"
Grid.Column="1"
Text="Observable Items" />
<ListView
Grid.Row="1"
Grid.Column="1"
ItemsSource="{x:Bind ObservableItems, Mode=OneWay}" />
<local:TestUserControl
Grid.Row="2"
Grid.Column="1"
ItemsSource="{x:Bind ObservableItems, Mode=OneWay}" />
</Grid>
</Grid>
</Page>
MainPage.xaml.cs
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace ItemsSourceTest;
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
AddItem();
}
public List<string> NonObservableItems { get; set; } = new();
public ObservableCollection<string> ObservableItems { get; set; } = new();
private int Counter { get; set; } = 0;
private void AddItem()
{
NonObservableItems.Add(Counter.ToString());
ObservableItems.Add(Counter.ToString());
Counter++;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
AddItem();
}
}

Wpf datacontext binding using MVVM between viewmodel and view

I just started learning MVVM and here is what seems to be basic question but I spent whole day trying to figure it out.
I have a solution that contains 3 projects one for Model, one for ViewModel and one for View. The Model contains a class that has 2 properties Text and CheckStatus.
The ViewModel has a list called listOfItems that has three items, each item has these 2 properties from the Model.
The View has a listView inside it there is a CheckBox. What is the proper way to bind the CheckBox content to the property Text?
Here is the model
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TheModel
{
public class CheckBoxListModel : INotifyPropertyChanged
{
private string text;
public string Text
{
get { return text; }
set
{
text = value;
RaiseChanged("Text");
}
}
private bool checkStatus;
public bool CheckStatus
{
get { return checkStatus; }
set
{
checkStatus = value;
RaiseChanged("CheckStatus");
}
}
private void RaiseChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
Here is the view model
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.ObjectModel;
using TheModel;
namespace TheViewModel
{
public class TheViewModel
{
public List<CheckBoxListModel> ListOfItems { get; set; }
public TheViewModelClass()
{
ListOfItems = new List<CheckBoxListModel>
{
new CheckBoxListModel
{
CheckStatus = false,
Text = "Item 1",
},
new CheckBoxListModel
{
CheckStatus = false,
Text = "Item 2",
},
new CheckBoxListModel
{
CheckStatus = false,
Text = "Item 3",
}
};
}
public static implicit operator List<object>(TheViewModelClass v)
{
throw new NotImplementedException();
}
}
}
and here is the View XAML
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ctrl="clr-namespace:TheView.Managers" xmlns:TheViewModel="clr-
namespace:TheViewModel;assembly=TheViewModel"
x:Class="TheView.Styles.ListViewDatabaseStyle">
<UserControl.DataContext>
<TheViewModel:TheViewModelClass/>
</UserControl.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="100"/>
</Grid.RowDefinitions>
<Button Content="Continue" Style="{StaticResource ButtonStyle}"
Margin="1104,27,40,40"/>
<ListView x:Name="listView1" SelectionMode="Multiple"
Style="{StaticResource ListViewStyle}" Margin="10,55,10,10"
ctrl:ListViewLayoutManager.Enabled="true" ItemsSource="
{Binding TheViewModelClass}" >
<ListView.View>
<GridView>
<GridViewColumn Header="Competency Items"
ctrl:ProportionalColumn.Width="1100"/>
</GridView>
</ListView.View>
<ListView.ItemContainerStyle >
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="IsSelected" Value="{Binding
CheckedStatus}"/>
<Setter Property="HorizontalContentAlignment"
Value="Stretch"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<CheckBox
Click="CheckBox_Click"
Content="{Binding Path=TheViewModelClass.Text}"
IsChecked="{Binding
Path=TheViewModelClass.CheckedStatus}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</UserControl>
Here is the View behind code, I know I shouldn't have something here but where should that part go?
using System.Windows;
using System.Windows.Controls;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
using System;
using System.Text;
using TheViewModel;
namespace TheView.Styles
{
public partial class ListViewDatabaseStyle : UserControl
{
public ListViewDatabaseStyle()
{
InitializeComponent();
}
public List<string> selectedNames = new List<string>();
private void CheckBox_Click(object sender, RoutedEventArgs e)
{
var ChkBox = sender as CheckBox;
var item = ChkBox.Content;
bool isChecked = ChkBox.IsChecked.HasValue ? ChkBox.IsChecked.Value
: false;
if (isChecked)
selectedNames.Add(item.ToString());
else
selectedNames.Remove(item.ToString());
}
}
}
This is all quite ridiculous.
Here is a much easier way which involves no external libraries, no additional housekeeping classes and interfaces, almost no magic, and is very flexible because you can have viewmodels that contain other viewmodels, and you get to instantiate each one of them, so you can pass constructor parameters to them:
For the viewmodel of the main window:
using Wpf = System.Windows;
public partial class TestApp : Wpf.Application
{
protected override void OnStartup( Wpf.StartupEventArgs e )
{
base.OnStartup( e );
MainWindow = new MainView();
MainWindow.DataContext = new MainViewModel( e.Args );
MainWindow.Show();
}
}
For all other viewmodels:
This is in MainViewModel.cs:
using Collections = System.Collections.Generic;
public class MainViewModel
{
public SomeViewModel SomeViewModel { get; }
public OtherViewModel OtherViewModel { get; }
public Collections.IReadOnlyList<string> Arguments { get; }
public MainViewModel( Collections.IReadOnlyList<string> arguments )
{
Arguments = arguments;
SomeViewModel = new SomeViewModel( this );
OtherViewModel = new OtherViewModel( this );
}
}
This in MainView.xaml:
[...]
xmlns:local="clr-namespace:the-namespace-of-my-wpf-stuff"
[...]
<local:SomeView DataContext="{Binding SomeViewModel}" />
<local:OtherView DataContext="{Binding OtherViewModel}" />
[...]
As you can see, a viewmodel can simply be a member (child) of another viewmodel; in this case SomeViewModel and OtherViewModel are children of MainViewModel. Then, in the XAML file of MainView, you can just instantiate each of the child views and specify their DataContext by Binding to the corresponding child viewmodels.
First of all. Set dependencies of projects. ViewModel must have access Model. (View and Model projects do not have to reference to other projects.) If i were you i would make a StartUp Project to transfer the control to ViewModel.
This "StartUp" project should be WPF, all of others should be "class library" but don't forget to add the required references to projects (For example the system.xaml for your view project to create usercontrols.)
Projects dependencies:
- StartUp --> ViewModel;
(- ViewModel --> View; or avoid this with DI)
- ViewModel --> Model;
(I should make another project for interfaces just this is just my perversions.)
StartUp Project:
Now in your startup (WPF) project should contains in (app.xaml.cs):
protected override void OnStartup(StartupEventArgs e)
{
// delete the startupuri tag from your app.xaml
base.OnStartup(e);
//this MainViewModel from your ViewModel project
MainWindow = new MainWindow(new MainViewModel());
}
The only one thing (Window) in your startup wpf project (to display your UserControls).
MainWindow.xaml content:
<Window x:Class="StartUp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" WindowState="Maximized" WindowStyle="None" AllowsTransparency="True">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" Content="{Binding Control}"/>
</Window>
(and xaml.cs)
public partial class MainWindow : Window
{
public MainWindow(INotifyPropertyChanged ViewModel)
{
InitializeComponent();
this.DataContext = ViewModel;
this.Show();
}
}
And Thats all your StartUp WPF project.
In this way we gave the control to your ViewModel project.
(Okay, its just an extra, but i should make a "ViewService" to handle my UserControls)
Interface to find all of View and match the View with ViewModel.
public interface IControlView
{
INotifyPropertyChanged ViewModel { get; set; }
}
I created a singleton to store and match my views with my viewmodels. (You can skip this part.) I defined this in Model project.
public class ViewService<T> where T : IControlView
{
private readonly List<WeakReference> cache;
public delegate void ShowDelegate(T ResultView);
public event ShowDelegate Show;
public void ShowControl<Z>(INotifyPropertyChanged ViewModel)
{
if (Show != null)
Show(GetView<Z>(ViewModel));
}
#region Singleton
private static ViewService<T> instance;
public static ViewService<T> GetContainer
{
get
{
if (instance == null)
{
instance = new ViewService<T>();
}
return instance;
}
}
private ViewService()
{
cache = new List<WeakReference>();
var types = AppDomain.CurrentDomain.GetAssemblies().SelectMany(s => s.GetTypes()).Where(r => typeof(T).IsAssignableFrom(r) && !r.IsInterface && !r.IsAbstract && !r.IsEnum);
foreach (Type type in types)
{
cache.Add(new WeakReference((T)Activator.CreateInstance(type)));
}
}
#endregion
private T GetView<Z>(INotifyPropertyChanged ViewModel)
{
T target = default(T);
foreach (var wRef in cache)
{
if (wRef.IsAlive && wRef.Target.GetType().IsEquivalentTo(typeof(Z)))
{
target = (T)wRef.Target;
break;
}
}
if(target==null)
target = (T)Activator.CreateInstance(typeof(Z));
if(ViewModel != null)
target.ViewModel = ViewModel;
return target;
}
}
And now you have got a "service" to show your UserControls in the mainwindow from your
ViewModel:
public class MainViewModel : INotifyPropertyChanged
{
private IControlView _control;
public IControlView Control
{
get
{
return _control;
}
set
{
_control = value;
OnPropertyChanged();
}
}
public MainViewModel()
{ //Subscribe for the ViewService event:
ViewService<IControlView>.GetContainer.Show += ShowControl;
// in this way, here is how to set a user control to the window.
ViewService<IControlView>.GetContainer.ShowControl<ListViewDatabaseStyle>(new TheViewModel(yourDependencyItems));
//you can call this anywhere in your viewmodel project. For example inside a command too.
}
public void ShowControl(IControlView ControlView)
{
Control = ControlView;
}
//implement INotifyPropertyChanged...
protected void OnPropertyChanged([CallerMemberName] string name = "propertyName")
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
If you don't want to use this "ViewService". Just create an UserControl instance, match DataContext of View with your ViewModel and give this view to Control property.
Here is your ViewModel with list (still in ViewMoldel project.)
public class TheViewModel
{
private readonly ObservableCollection<ISelectable> listOfItems;
public ObservableCollection<ISelectable> ListOfItems
{
get { return listOfItems; }
}
public ICommand SaveCheckedItemsText{
get{ return new RelayCommand(CollectNamesOfSelectedElements);}
}
public IEnumerable<ISelectable> GetSelectedElements
{
get { return listOfItems.Where(item=>item.CheckStatus); }
}
public TheViewModel(IList<ISelectable> dependencyItems)
{
listOfItems= new ObservableCollection<ISelectable>(dependencyItems);
}
//here is your list...
private List<string> selectedNames
//use this...
private void CollectNamesOfSelectedElements()
{
selectedNames = new List<string>();
foreach(ISelectable item in GetSelectedElements)
{
//you should override the ToString in your model if you want to do this...
selectedNames.Add(item.ToString());
}
}
}
RelayCommand article
View: (Keep here all of your usercontrols.)
In your UserControl (xaml):
<UserControl x:Class="View.ListViewDataStyle"
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" namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
mc:Ignorable="d">
<Button Command={Binding SaveCheckedItemsText}/>
<!-- Another content -->
<ListView ItemsSource="{Binding ListOfItems}">
<ListView.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Text}" IsChecked="{Binding CheckedStatus}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</UserControl>
And with interface here is the xaml.cs code (for UserControls):
public partial class ListViewDatabaseStyle : UserControl, IControlView
{
public ListViewDatabaseStyle ()
{
InitializeComponent();
}
public INotifyPropertyChanged ViewModel
{
get
{
return (INotifyPropertyChanged)DataContext;
}
set
{
DataContext = value;
}
}
}
And the last one is the Model project with your models:
public interface ISelectable
{
bool CheckStatus { get; set; }
}
public class CheckBoxListModel : INotifyPropertyChanged, ISelectable
{
private string text;
public string Text
{
get { return text; }
set
{
text = value;
RaiseChanged("Text");
}
}
private bool checkStatus;
public bool CheckStatus
{
get { return checkStatus; }
set
{
checkStatus = value;
RaiseChanged("CheckStatus");
}
}
private void RaiseChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
Excuse me for english grammar mistakes, i hope you understood my post.
Update:
Use the DI techn. to avoid the reference to view from viewmodel. DI service will inject the correct object with constructor injection.
<UserControl.DataContext>
<TheViewModel:TheViewModelClass/>
</UserControl.DataContext>
<ListView ItemsSource="{Binding ListOfItems}">
<ListView.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Text}" IsChecked="{Binding CheckedStatus}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

ItemsControl ItemSource Binding not updating

I used to just create a block of text by converting a list of strings to one string with newlines. This Binding worked; updated when it was supposed to and all, but I'm trying to move the list of text into an ItemsControl as they will need to be hyperlinks at some point in the future. Problem: The ItemsControl does not change when the PropertyChangeEvent is fired. The Relevant Code is as follows:
Xaml
<local:BaseUserControl x:Class="BAC.Windows.UI.Views.ErrorsView"
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:local="clr-namespace:BAC.Windows.UI.Views"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
...
<ItemsControl ItemsSource="{Binding Path=ErrorMessages}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"></TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!--<TextBlock VerticalAlignment="Center" Visibility="{Binding ErrorMessages, Converter={StaticResource VisibleWhenNotEmptyConverter}}" Text="{Binding ErrorMessages, Converter={StaticResource ErrorMessagesToTextConverter}}">
(What I used to use)
</TextBlock>-->
...
</local:BaseUserControl>
ViewModel
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using ASI.Core.Core;
using ASI.Core.DTO;
using ASI.Core.Extensions;
using ASI.Core.Mappers;
using BAC.Core.Resources;
using BAC.Core.Services;
using BAC.Core.ViewModels.Views;
namespace BAC.Core.ViewModels
{
public interface IErrorsViewModel : IViewModel<IErrorsView>
{
}
public class ErrorsViewModel : BaseViewModel<IErrorsView>, IErrorsViewModel
{
...
private readonly ErrorDTO _errorDTO;
private readonly ErrorDTO _warningDTO;
public ErrorsViewModel(...) : base(view)
{
...
//Just added this string to know that it's at least binding. This Message displays, and never changes.
ErrorMessages = new List<string>() {"Simple Message"};
//Tells the View to bind dataContext to Viewmodel
Edit();
}
private void errorDTOOnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
{
ErrorDTO dto;
if (!string.Equals(propertyChangedEventArgs.PropertyName, nameof(dto.HasError))) return;
ErrorMessages.Clear();
_errorDTO.ErrorMessages.Each(x => ErrorMessages.Add(Constants.Captions.Errors + ": " + x));
_warningDTO.ErrorMessages.Each(x => ErrorMessages.Add(Constants.Captions.Warnings + ": " + x));
OnPropertyChanged(() => ErrorMessages);
OnPropertyChanged(() => HasError);
OnPropertyChanged(() => HasWarning);
}
...
public bool HasError => _errorDTO.HasError;
public bool HasWarning => _warningDTO.HasError;
public IList<string> ErrorMessages { get; set; }
...
}
And just because I know people may ask to see it...
public class BaseNotifyPropertyChanged : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public void OnPropertyChanged<T>(Expression<Func<T>> propertyExpression)
{
var body = propertyExpression.Body as MemberExpression;
if (body != null)
OnPropertyChanged(body.Member.Name);
}
protected void OnEvent(Action action)
{
try
{
action();
}
catch
{ }
}
}
I'm sure it's something stupidy simple I'm doing, but the harder I look, the more I get frusterated by what should something simple. Why does the binding work for all other conrols except ItemSource? What's so special about it?
I'll also add anotehr explanation (Even though I know this is old).
The reason this will not update the property is that the List object is not actually changing, so the ListView will not update the list. The only way to do this without using "ObservableCollection" is to create a brand new list on each property change like so:
private void errorDTOOnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
{
if (!string.Equals(propertyChangedEventArgs.PropertyName, nameof(dto.HasError))) return;
OnPropertyChanged(() => ErrorMessages);
}
public List<string> ErrorMessages => getErrorMessages();
private List<string> getErrorMessages() {
//create list in a manner of your choosing
}
Hopefully that helps people when they run into this.
So I was able to get your code to work by using an ObservableCollection instead of the List. The ObservableCollection generates a list changed notification automatically when its collection is changed. Below is my sample code. I use a timer to update the error list every second.
<Window x:Class="TestEer.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:TestEer"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525" DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<ItemsControl ItemsSource="{Binding Path=ErrorMessages}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
using System.Collections.ObjectModel;
using System.Timers;
using System.Windows;
using System.Windows.Data;
namespace TestEer
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private Timer _timer;
private readonly object _sync = new object( );
public MainWindow( )
{
InitializeComponent( );
BindingOperations.EnableCollectionSynchronization( ErrorMessages, _sync );
_timer = new Timer
{
AutoReset = true,
Interval = 1000
};
_timer.Elapsed += _timer_Elapsed;
_timer.Enabled = true;
_timer.Start( );
}
private void _timer_Elapsed( object sender, ElapsedEventArgs e )
{
ErrorMessages.Add( $"Error # {e.SignalTime}" );
}
public ObservableCollection<string> ErrorMessages { get; } = new ObservableCollection<string>( );
}
}
We set up the OnPropertyChanged() method in the get set methods before the constructor and this seemed to work!
private bool _theString;
public bool TheString
{
get { return _theString; }
set { _theString = value; OnPropertyChanged(); }
}
Use {Binding TheString} in your .xaml.
Hope this helps!

Iterating selected items in Windows App Store Gridview

I know this is a long one, but please bear with me.
I have created a windows app store program very similar to Laurent Bugnion's "MyFriends" program in the MVVM light samples using the MVVM light framework.
In his program he uses the SelectedItem property of the gridview to keep track of which item is the selected item.
The problem is, I give the user the ability to select multiple items on the GridView and then operate on them using a button on the App Bar. For this SelectedItem will not work.
Does anyone know how to make this work with a multiselect GridView? I have tried the IsSelected property of the GridViewItem based on some articles on WPF, but this doesn't seem to work. The SelectedTimesheets getter always come back empty when called. Here is what I have so far:
MainPage.xaml (bound to a MainViewModel with a child TimesheetViewModel observable collection):
<GridView
x:Name="itemGridView"
IsItemClickEnabled="True"
ItemsSource="{Binding Timesheets}"
ItemTemplate="{StaticResource TimesheetTemplate}"
Margin="10"
Grid.Column="0"
SelectionMode="Multiple"
helpers:ItemClickCommand.Command="{Binding NavigateTimesheetCommand}" RenderTransformOrigin="0.738,0.55" >
<GridView.ItemContainerStyle>
<Style TargetType="GridViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
</Style>
</GridView.ItemContainerStyle>
</GridView>
MainViewModel (cut down from full code):
public class MainViewModel : ViewModelBase
{
private readonly IDataService _dataService;
private readonly INavigationService _navigationService;
/// <summary>
/// Initializes a new instance of the MainViewModel class.
/// </summary>
public MainViewModel(IDataService dataService, INavigationService navigationService)
{
_dataService = dataService;
_navigationService = navigationService;
Timesheets = new ObservableCollection<TimesheetViewModel>();
ExecuteRefreshCommand();
}
public ObservableCollection<TimesheetViewModel> Timesheets
{
get;
private set;
}
public IEnumerable<TimesheetViewModel> SelectedTimesheets
{
get { return Timesheets.Where(o => o.IsSelected); }
}
private async void ExecuteRefreshCommand()
{
var timesheets = await _dataService.GetTimesheets("domain\\user");
if (timesheets != null)
{
Timesheets.Clear();
foreach (var timesheet in timesheets)
{
Timesheets.Add(new TimesheetViewModel(timesheet));
}
}
}
}
TimesheetViewModel:
public class TimesheetViewModel: ViewModelBase
{
public bool IsSelected { get; set; }
public Timesheet Model
{
get;
private set;
}
public TimesheetViewModel(Timesheet model)
{
Model = model;
}
}
If I set the IsSelected property manually, the SelectedTimesheets lambda works, so the problem is somewhere in the binding of the XAML to the IsSelected property.
Any help would be appreciated.
Sure, I know what you mean. Too bad this isn't automagic, but it isn't. The solution involves a simple custom GridView that inherits from GridView. Nothing too crazy, that is, if you let it sink in. Here's the code, I just tested it:
Here's your XAML:
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<Grid.ColumnDefinitions >
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<local:MyGridView ItemsSource="{Binding Items}" SelectionMode="Multiple"
BindableSelectedItems="{Binding Selected}" />
<local:MyGridView Grid.Column="1" ItemsSource="{Binding Selected}" />
</Grid>
Here's your view model (super-simplified):
public class ViewModel
{
ObservableCollection<string> m_Items
= new ObservableCollection<string>(Enumerable.Range(1, 100).Select(x => x.ToString()));
public ObservableCollection<string> Items { get { return m_Items; } }
ObservableCollection<object> m_Selected = new ObservableCollection<object>();
public ObservableCollection<object> Selected { get { return m_Selected; } }
}
And here's your custom gridview:
public class MyGridView : GridView
{
public ObservableCollection<object> BindableSelectedItems
{
get { return GetValue(BindableSelectedItemsProperty) as ObservableCollection<object>; }
set { SetValue(BindableSelectedItemsProperty, value as ObservableCollection<object>); }
}
public static readonly DependencyProperty BindableSelectedItemsProperty =
DependencyProperty.Register("BindableSelectedItems",
typeof(ObservableCollection<object>), typeof(MyGridView),
new PropertyMetadata(null, (s, e) =>
{
(s as MyGridView).SelectionChanged -= (s as MyGridView).MyGridView_SelectionChanged;
(s as MyGridView).SelectionChanged += (s as MyGridView).MyGridView_SelectionChanged;
}));
void MyGridView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (BindableSelectedItems == null)
return;
foreach (var item in BindableSelectedItems.Where(x => !this.SelectedItems.Contains(x)).ToArray())
BindableSelectedItems.Remove(item);
foreach (var item in this.SelectedItems.Where(x => !BindableSelectedItems.Contains(x)))
BindableSelectedItems.Add(item);
}
}
Just one new property BindableSelectedItems.
Best of luck!
#Jerry-Nixon-MSFT's answer spurred me on to rethink it (thanks to him) and I came up with the following solution.
Firstly I changed the XAML to accept a new helper method SelectionChangedCommand.Command and bound it to a RelayCommand called SelectionChangedCommand in my view model
MainPage.xaml
<GridView
x:Name="itemGridView"
IsItemClickEnabled="True"
ItemsSource="{Binding Timesheets}"
ItemTemplate="{StaticResource TimesheetTemplate}"
Margin="10"
Grid.Column="0"
SelectionMode="Multiple"
helpers:ItemClickCommand.Command="{Binding NavigateTimesheetCommand}"
helpers:SelectionChangedCommand.Command="{Binding SelectionChangedCommand}
"RenderTransformOrigin="0.738,0.55" >
</GridView>
I then added a SelectionChangedCommand helper class under my helpers namespace to translate the SelectionChanged event into an ICommand
namespace TimesheetManager.Helpers
{
public class SelectionChangedCommand
{
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached("Command", typeof(ICommand),
typeof(SelectionChangedCommand), new PropertyMetadata(null,
OnCommandPropertyChanged));
public static void SetCommand(DependencyObject d, ICommand value)
{
d.SetValue(CommandProperty, value);
}
public static ICommand GetCommand(DependencyObject d)
{
return (ICommand)d.GetValue(CommandProperty);
}
private static void OnCommandPropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var control = d as ListViewBase;
if (control != null)
control.SelectionChanged += OnSelectionChanged;
}
private static void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var control = sender as ListViewBase;
var command = GetCommand(control);
if (command != null && command.CanExecute(e))
command.Execute(e);
}
}
}
This binds the SelectionChanged event of any control which inherits from ListViewBase (our gridview) to a method called OnSelectionChanged. OnSelectionChanged subsequently passes the SelectionChangedEventArgs from the control to the RelayCommand binding in the XAML.
Finally in MainViewModel, I process the RelayCommand and set the IsSelected flag:
MainViewModel:
private RelayCommand<object> _selectionChangedCommand;
/// <summary>
/// Gets the SelectionChangedCommand.
/// </summary>
public RelayCommand<object> SelectionChangedCommand
{
get
{
return _selectionChangedCommand ?? (_selectionChangedCommand = new RelayCommand<object>
((param) => ExecuteSelectionChangedCommand(param)));
}
}
private void ExecuteSelectionChangedCommand(object sender)
{
var x = sender as SelectionChangedEventArgs;
foreach (var item in x.AddedItems)
((TimesheetViewModel)item).IsSelected = true;
foreach (var item in x.RemovedItems)
((TimesheetViewModel)item).IsSelected = false;
}
I know there is a fair amount of casting going on, but we are limited to object by the ICommand interface.
Hope this helps.

WPF ComboBox Binding works if previous record had no matching item in the ItemsSource collection and fails if it did

I have the following WPF Combobox:
<Window.Resources>
<CollectionViewSource x:Key="performanceItemsource" Source="{Binding Path=SelectedReport.Performances}" >
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="Name"/>
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
</Window.Resources>
...
<ComboBox Name="cbxPlanPerf" Grid.ColumnSpan="2"
SelectedValuePath="MSDPortfolioID" DisplayMemberPath="Name"
SelectedValue="{Binding Path=PlanPerfID}"
ItemsSource="{Binding Source={StaticResource performanceItemsource}}"/>
The Source for the CollectionViewSource is:
public List<MSDExportProxy> Performances
{
get
{
if (Portfolio != null)
{
return (from a in Portfolio.Accounts where a.MSDPortfolioID != null select new MSDExportProxy(a))
.Concat<MSDExportProxy>(from g in Portfolio.Groups where g.MSDPortfolioID != null select new MSDExportProxy(g))
.Concat<MSDExportProxy>(from p in new[] { Portfolio } where p.MSDPortfolioID != null select new MSDExportProxy(p))
.ToList<MSDExportProxy>();
}
return new List<MSDExportProxy>();
}
}
The bound property PlanPerfID is a string.
I move between records using a ListBox control. The ComboBox works fine if the previous record had no items in its ComboBox.ItemsSource. If there were any items in the previous record's ComboBox.ItemsSource then the new record won't find its matching item in the ItemsSource collection. I've tried setting the ItemsSource in both XAML and the code-behind, but nothing changes this odd behavior. How can I get this darn thing to work?
Try using ICollectionViews in combination with IsSynchronizedWithCurrentItem property when handling lists / ObservableCollection in Xaml. The ICollectionView in the viewmodel can handle all the things needed, e.g. sorting, filtering, keeping track of selections and states.
Xaml:
<Window x:Class="ComboBoxBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ListBox Grid.Column="0"
ItemsSource="{Binding Reports}"
DisplayMemberPath="Name"
IsSynchronizedWithCurrentItem="True" />
<ComboBox Grid.Column="1"
ItemsSource="{Binding CurrentReport.Performances}"
DisplayMemberPath="Name"
IsSynchronizedWithCurrentItem="True" />
</Grid>
</Window>
ViewModel:
public class ViewModel : INotifyPropertyChanged
{
private readonly IReportService _reportService;
private ObservableCollection<ReportViewModel> _reports = new ObservableCollection<ReportViewModel>();
private PerformanceViewModel _currentPerformance;
private ReportViewModel _currentReport;
public ObservableCollection<ReportViewModel> Reports
{
get { return _reports; }
set { _reports = value; OnPropertyChanged("Reports");}
}
public ReportViewModel CurrentReport
{
get { return _currentReport; }
set { _currentReport = value; OnPropertyChanged("CurrentReport");}
}
public PerformanceViewModel CurrentPerformance
{
get { return _currentPerformance; }
set { _currentPerformance = value; OnPropertyChanged("CurrentPerformance");}
}
public ICollectionView ReportsView { get; private set; }
public ICollectionView PerformancesView { get; private set; }
public ViewModel(IReportService reportService)
{
if (reportService == null) throw new ArgumentNullException("reportService");
_reportService = reportService;
var reports = _reportService.GetData();
Reports = new ObservableCollection<ReportViewModel>(reports);
ReportsView = CollectionViewSource.GetDefaultView(Reports);
ReportsView.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
ReportsView.CurrentChanged += OnReportsChanged;
ReportsView.MoveCurrentToFirst();
}
private void OnReportsChanged(object sender, EventArgs e)
{
var selectedReport = ReportsView.CurrentItem as ReportViewModel;
if (selectedReport == null) return;
CurrentReport = selectedReport;
if(PerformancesView != null)
{
PerformancesView.CurrentChanged -= OnPerformancesChanged;
}
PerformancesView = CollectionViewSource.GetDefaultView(CurrentReport.Performances);
PerformancesView.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
PerformancesView.CurrentChanged += OnPerformancesChanged;
PerformancesView.MoveCurrentToFirst();
}
private void OnPerformancesChanged(object sender, EventArgs e)
{
var selectedperformance = PerformancesView.CurrentItem as PerformanceViewModel;
if (selectedperformance == null) return;
CurrentPerformance = selectedperformance;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
I found a quick and dirty solution to my problem. I just happen to have a public NotifyPropertyChanged() method on my Report entity and I discovered that if I called SelectedReport.NotifyPropertyChanged("PlanPerfID") in the Report ListBox's SelectionChanged event that it was enough of a jolt to get the ComboBox to re-evaluate and find its matching item in the ItemsSource. Yeah, it's KLUGE...
UPDATE: I also wound up needing to add SelectedReport.NotifyPropertyChanged("Performances") for some situations...
UPDATE 2: Okay, turns out the above wasn't bullet proof and I ran across a situation that broke it so I had to come up with a better workaround:
Altered the SelectedReport property in the Window's code-behind, adding a private flag (_settingCombos) to keep the Binding from screwing up the bound values until the dust has settled from changin the ItemSource:
private bool _settingCombos = false;
private Report _SelectedReport;
public Report SelectedReport
{
get { return _SelectedReport; }
set
{
_settingCombos = true;
_SelectedReport = value;
NotifyPropertyChanged("SelectedReport");
}
}
Created a proxy to bind to in the Window code-behind that will refuse to update the property's value if the _settingCombos flag is true:
public string PlanPerfID_Proxy
{
get { return SelectedReport.PlanPerfID; }
set
{
if (!_settingCombos)
{
SelectedReport.PlanPerfID = value;
NotifyPropertyChanged("PlanPerfID_Proxy");
}
}
}
Added an extra Notification in the Report ListBox's SelectionChanged event along with code to reset the _settingCombos flag back to false:
private void lbxReports_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
//KLUGE: Couldn't get the ComboBoxes associated with these properties to work right
//this forces them to re-evaluate after the Report has loaded
if (SelectedReport != null)
{
NotifyPropertyChanged("PlanPerfID_Proxy");
_settingCombos = false;
}
}
Bound the ComboBox to the PlanPerfID_Proxy property (instead of directly to the SelectedReport.PlanPerfID property.
Wow, what a hassle! I think that this is simply a case of .NET's binding logic getting confused by the dynamic nature of the ComboBox.ItemSource, but this seems to have fixed it. Hope it helps someone else.

Categories

Resources