equivalent code in wpf - wpf

In Winforms I used the below code to select the specific item in DataGridView.
If DGView.Rows(row).Cells(0).Value.StartsWith(txtBoxInDGView.Text, StringComparison.InvariantCultureIgnoreCase) Then
DGView.Rows(row).Selected = True
DGView.CurrentCell = DGView.SelectedCells(0)
End If
Can anyone give the equivalent code for WPF DataGrid?

WPF is more data-driven than WinForms. It means it's better to work with objects (that represent your data) than to deal with UI elements.
You should have a collection that is the items source of the data grid. In the same data context, you should have a property that will hold the selected item (same type as the items in the collection). All properties should notify change.
Considering you have MyItem class for each row in data grid, the code would be something like this:
In the class that is the data context of your data grid:
public ObservableCollection<MyItem> MyCollection {get; set;}
public MyItem MySelectedItem {get; set;} //Add change notification
private string _myComparisonString;
public string MyComparisonString
{
get{return _myComparisonString;}
set
{
if _myComparisonString.Equals(value) return;
//Do change notification here
UpdateSelection();
}
}
.......
private void UpdateSelection()
{
MyItem theSelectedOne = null;
//Do some logic to find the item that you need to select
//this foreach is not efficient, just for demonstration
foreach (item in MyCollection)
{
if (theSelectedOne == null && item.SomeStringProperty.StartsWith(MyComparisonString))
{
theSelectedOne = item;
}
}
MySelectedItem = theSelectedOne;
}
In your XAML, you'd have a TextBox and a DataGrid, similar to this:
<TextBox Text="{Binding MyComparisonString, UpdateSourceTrigger=PropertyChanged}"/>
....
<DataGrid ItemsSource="{Binding MyCollection}"
SelectedItem="{Binding MySelectedItem}"/>
This way, your logic is independent from your UI. As long as you have change notification - the UI will update the properties and the properties will affect the UI.
[Treat code above as a pseudo-code, I'm not on my dev machine currently]

Related

How to filter combobox items based on the input text?

I would like to display a combo box whose items are supplied by the view model.
The combo box should be editable.
Based on the text currently input by the user, the combo box items should be filtered.
I am trying to apply the following solution pointed out in various resources on the topic (such as that question, that question, that article, that question, that blogpost, that tutorial, etc.):
My view model provides a collection view around the items.
I have two-way bound the Text property of the combo box to a CustomText property in my view model.
The Filter predicate on the collection view is set to check items based on whether their display name contains the CustomText.
When CustomText is changed, the Refresh method on the items collection view is invoked.
I'd expect this to update the list of items in the combo box dropdown list whenever I modify the text. Unfortunately, the list remains the same.
If I place a breakpoint in my Filter predicate, it gets hit, but somehow, not always for each item.
Here is a minimal example:
Xaml for the window:
<Window x:Class="ComboBoxFilterTest.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:ComboBoxFilterTest"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<ComboBox
VerticalAlignment="Center"
ItemsSource="{Binding Items}"
DisplayMemberPath="Name"
IsEditable="True"
Text="{Binding CustomText}"
IsTextSearchEnabled="False"/>
</Grid>
</Window>
The code-behind for the window:
using System.Windows;
namespace ComboBoxFilterTest
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
}
}
And the view model (here with the Item data class, which would normally reside elsewhere):
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Data;
namespace ComboBoxFilterTest
{
public class MainViewModel : INotifyPropertyChanged
{
private sealed class Item
{
public int Id { get; set; }
public string Name { get; set; }
}
public MainViewModel()
{
Items = new CollectionView(items)
{
Filter = item =>
{
if (string.IsNullOrEmpty(customText))
{
return true;
}
if (item is Item typedItem)
{
return typedItem.Name.ToLowerInvariant().Contains(customText.ToLowerInvariant());
}
return false;
}
};
}
private readonly ObservableCollection<Item> items = new ObservableCollection<Item>
{
new Item{ Id = 1, Name = "ABC" },
new Item{ Id = 2, Name = "ABCD" },
new Item{ Id = 3, Name = "XYZ" }
};
public ICollectionView Items { get; }
private string customText = "";
public event PropertyChangedEventHandler PropertyChanged;
public string CustomText
{
get => customText;
set
{
if (customText != value)
{
customText = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CustomText)));
Items.Refresh();
}
}
}
}
}
Basically, I think I'm doing the same as what is described in another question, but apparently, something is still different as it doesn't work in my case.
Note that one slight difference is that I am not using CollectionViewSource.GetDefaultView, as I want to have several differently filtered views on the same collection rather than obtaining the default view.
Note that as a workaround, I could of course just return the appropriately filtered enumerable of items myself and fire a property changed event for such an enumerable property each time the filter changes. However, I understand relying on collection views is the proper WPF way, so I would prefer to do it "correctly".
I think I found a solution: As was suggested in an answer on a related topic, I used ListCollectionView instead of CollectionView.
For some reason, it works with ListCollectionView while it doesn't with CollectionView, even though the latter does not give any indication that it shouldn't (e.g. CollectionView.CanFilter returns true).
I am going to accept this answer of my own for now, though if someone can provide an answer that actually provides an explanation for this behaviour, I'll be glad to accept such an answer instead.
The recommended pattern to avoid any problems like the one you are experiencing is to use CollectionViewSource as binding source.
As also mentioned in the docs, you should never create instances of CollectionView manually. You have to use a specialized sub-type according to the actual type of the source collection:
"You should not create objects of this class [CollectionView] in your code. To create a collection view for a collection that only implements IEnumerable, create a CollectionViewSource object, add your collection to the Source property, and get the collection view from the View property." Microsoft Docs: CollectionView
CollectionViewSource internally does the type checking for you and creates a properly initialized ICollectionView implementation, that is appropriate for the actual source collection. CollectionViewSource, whether created in XAML or C#, is the recommended way to obtain an instance of ICollectionView, if the default view is not sufficient:
public ICollectionView Items { get; }
public CollectionViewSource ItemsViewSource { get; }
public ctor()
{
ObservableCollection<object> items = CreateObservableItems();
this.ItemsViewSource = new CollectionViewSource() {Source = items};
this.Items = this.ItemsViewSource.View;
}

How to pass parameter to ComboBox ItemsSource binding?

I am working with WPF for the first time, so please bear with me.
I have a combobox, which is meant to generically display some lookup data. The models for the different types of lookups are exactly the same, just different data sources which are retrieved via a single method call passing different enumerations to control the returned data set. Fairly simple stuff.
public sealed class MyDataProvider
{
public enum Types
{
Green,
Blue,
Orange
}
private readonly ConcurrentDictionary<string, ObservableCollection<LookUpVm>> _lookupData =
new ConcurrentDictionary<string, ObservableCollection<LookUpVm>>();
private static readonly Lazy<MyDataProvider> lazy =
new Lazy<MyDataProvider>(() => new MyDataProvider());
public static MyDataProvider Instance => lazy.Value;
private MyDataProvider()
{
}
public ObservableCollection<LookUpVm> GreenLookupDataSource => GetLookupDataSource(Types.Green);
public ObservableCollection<LookUpVm> GetLookupDataSource(Types lookupEnum)
{
ObservableCollection<LookUpVm> lookupDataSource;
if (_lookupData.TryGetValue(lookupEnum, out lookupDataSource))
return lookupDataSource;
lookupDataSource = new ObservableCollection<LookUpVm>();
var returnedlookupDataSource =
SomeMasterSource.GetlookupDataSourceBylookupEnum(lookupEnum).OrderBy(ia => ia.Name);
foreach (var returnedLookupData in returnedlookupDataSource)
{
lookupDataSource.Add(returnedLookupData);
}
_lookupData.TryAdd(lookupEnum, lookupDataSource);
return lookupDataSource;
}
}
This works great for the 0th iteration, where I create a GreenLookupComboBox.
<ComboBox ItemsSource="{Binding Source={x:Static objectDataProviders:MyDataProvider.Instance},
Path=GreenLookupDataSource}" />
However, what I really need to be able to do is to set up a combobox which can have its Types enum value set on the parent View, which would then call directly to the GetLookupDataSource and pass the enum. We have several dozen lookup types, and defining a new property for each feels less than ideal. Something like the below for the control view...
<ComboBox ItemsSource="{Binding Source={x:Static objectDataProviders:MyDataProvider.Instance},
Path=GetLookupDataSource}" />
And something like the below for where I use the lookup control.
<local:MyLookupControl Type=Types.Green />
Is this even possible?
EDIT:
Here's an example of what I'm trying to accomplish.
I have two key-value pairs of lists.
ListOne
1 - A
2 - B
3 - C
and
ListTwo
1 - X
2 - Y
3 - Z
They are accessible by calling the method GetList(Enum.LookupType). They share the same ViewModel and View. However, I need to place both of them on a form for my users to select from.
I'm looking for some way to use XAML like the following on the View they appear on.
<local:MyLookupControl Method=GetList Parameter=Enum.ListOne/>
<local:MyLookupControl Method=GetList Parameter=Enum.ListTwo />
This should display a pair of comboboxes, one bound to ListOne and one bound to ListTwo.
You're essentially just trying to set up databinding on a couple controls. This is simple then so long as you have the correct datacontext for the view.
Controls can be bound to properties (which is exactly what you are looking for).
Using your edited example here is how you would do that:
private ObservableCollection<string> _listOne;
private ObservableCollection<string> _listTwo;
private string _selectedListOneItem;
private string _selectedListTwoItem;
public ObservableCollection<string> ListOne
{
get { return _listOne; }
set { _listOne = value; }
}
public ObservableCollection<string> ListTwo
{
get { return _listTwo; }
set { _listTwo = value; }
}
public string SelectedListOneItem
{
get { return _selectedListOneItem; }
set { _selectedListOneItem = value; }
}
public string SelectedListTwoItem
{
get { return _selectedListTwoItem; }
set { _selectedListTwoItem = value; }
}
XAML:
<ComboBox ItemsSource="{Binding ListOne}" SelectedItem="{Binding SelectedListOneItem}"/>
<ComboBox ItemsSource="{Binding ListTwo}" SelectedItem="{Binding SelectedListTwoItem}"/>
You have several options in how you want to load or get your lists. You can either load them in the constructor or for something a bit heavier is load them every time you "get" in the property. I would recommend loading those in the constructor.
What I provided is basically autoprops and can even be further simplified but I wanted to show you that you can also write code in the getter and setter of those properties to further expand on the items. For instance, you may want something on the background to fire off when SelectedListOneItem changes. In this case on the SET of SelectedListOneItem you can set the value but then also run a method/function which may update other properties.
WPF is very dependent on properties to bind between ViewModels and Views. In your response before the edit you are using fields which can't be bound to controls in a view.
EDIT:
If you do plan on updating properties in the ViewModel that would change things on the view you are also going to want to look into INotifyPropertyChanged. By implementing INotifyPropertyChanged the view will be updated/notified when properties are changing. INotifyPropertyChanged comes with it's own event that you must invoke in the setting of a property. Here is also a very helpful method you can call that will fire this event for you much easier.
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
You call this like so:
public string SelectedListOneItem
{
get { return _selectedListOneItem; }
set
{
_selectedListOneItem = value;
OnPropertyChanged();
}
}
That way if anything else in the ViewModel updates SelectedListOneItem that your view will make the appropriate change. In this case it would make the combobox select the new value you set in SelectedListOneItem.

Deselecting ComboBoxItems in MVVM

I am using a standard wpf/mvvm application where i bind combo boxes to collections on a ViewModel.
I need to be able to de-select an item from the dropdown. Meaning, users should be able to select something, and later decide that they want to un-select it (select none) for it. the problem is that there are no empty elements in my bound collection
my initial thought was simply to insert a new item in the collection which would result having an empty item on top of the collection.
this is a hack though, and it affects all code that uses that collection on the view model.
for example if someone was to write
_myCollection.Frist(o => o.Name == "foo")
this will throw a null reference exception.
possible workaround is:
_myCollection.Where(o => o != null).First(o => o.Name == "foo");
this will work, but no way to ensure any future uses of that collection won't cause any breaks.
what's a good pattern / solution for being able to adding an empty item so the user can de-select. (I am also aware of CollectionView structure, but that seems like a overkill for something so simple)
Update
went with #hbarck suggestion and implemented CompositeCollection (quick proof of concept)
public CompositeCollection MyObjects {
get {
var col = new CompositeCollection();
var cc1 = new CollectionContainer();
cc1.Collection = _actualCollection;
var cc2 = new CollectionContainer();
cc2.Collection = new List<MyObject>() { null }; // PROBLEM
col.Add(cc2);
col.Add(cc1);
return col;
}
}
this code work with existing bindings (including SelectedItem) which is great.
One problem with this is, that if the item is completely null, the SelectedItem setter is never called upon selecting it.
if i modify that one line to this:
cc2.Collection = new List<MyObject>() { new MyObject() }; // PROBLEM
the setter is called, but now my selected item is just a basic initialized class instead of null.. i could add some code in the setter to check/reset, but that's not good.
I think the easiest way would be to use a CompositeCollection. Just append your collection to another collection which only contains the empty item (null or a placeholder object, whatever suites your needs), and make the CompositeCollection the ItemsSource for the ComboBox. This is probably what it is intended for.
Update:
This turns out to be more complicated than I first thought, but actually, I came up with this solution:
<Window x:Class="ComboBoxFallbackValue"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:t="clr-namespace:TestWpfDataBinding"
xmlns:s="clr-namespace:System;assembly=mscorlib"
xmlns:w="clr-namespace:System.Windows;assembly=WindowsBase"
Title="ComboBoxFallbackValue" Height="300" Width="300">
<Window.Resources>
<t:TestCollection x:Key="test"/>
<CompositeCollection x:Key="MyItemsSource">
<x:Static Member="t:TestCollection.NullItem"/>
<CollectionContainer Collection="{Binding Source={StaticResource test}}"/>
</CompositeCollection>
<t:TestModel x:Key="model"/>
<t:NullItemConverter x:Key="nullItemConverter"/>
</Window.Resources>
<StackPanel>
<ComboBox x:Name="cbox" ItemsSource="{Binding Source={StaticResource MyItemsSource}}" IsEditable="true" IsReadOnly="True" Text="Select an Option" SelectedItem="{Binding Source={StaticResource model}, Path=TestItem, Converter={StaticResource nullItemConverter}, ConverterParameter={x:Static t:TestCollection.NullItem}}"/>
<TextBlock Text="{Binding Source={StaticResource model}, Path=TestItem, TargetNullValue='Testitem is null'}"/>
</StackPanel>
Basically, the pattern is that you declare a singleton NullInstance of the class you use as items, and use a Converter which converts this instance to null when setting the VM property. The converter can be written universally, like this (it's VB, I hope you don't mind):
Public Class NullItemConverter
Implements IValueConverter
Public Function Convert(value As Object, targetType As System.Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.Convert
If value Is Nothing Then
Return parameter
Else
Return value
End If
End Function
Public Function ConvertBack(value As Object, targetType As System.Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.ConvertBack
If value Is parameter Then
Return Nothing
Else
Return value
End If
End Function
End Class
Since you can reuse the converter, you can set this all up in XAML; the only thing that remains to be done in code is to provide the singleton NullItem.
Personally, I tend to add an "empty" version of whatever object is in my collection I'm binding to. So, for example, if you're binding to a list of strings, then in your viewmodel, insert an empty string at the beginning of the collection. If your Model has the data collection, then wrap it with another collection in your viewmodel.
MODEL:
public class Foo
{
public List<string> MyList { get; set;}
}
VIEW MODEL:
public class FooVM
{
private readonly Foo _fooModel ;
private readonly ObservableCollection<string> _col;
public ObservableCollection<string> Col // Binds to the combobox as ItemsSource
{
get { return _col; }
}
public string SelectedString { get; set; } // Binds to the view
public FooVM(Foo model)
{
_fooModel = model;
_col= new ObservableCollection<string>(_fooModel.MyList);
_col.Insert(0, string.Empty);
}
}
You could also extend the ComboBox to enable de-selecting. Add one or more hooks (eg, pressing the escape key) that allow the user to set the SelectedItem to null.
using System.Windows.Input;
public class NullableComboBox : ComboBox
{
public NullableComboBox()
: base()
{
this.KeyUp += new KeyEventHandler(NullableComboBox_KeyUp);
var menuItem = new MenuItem();
menuItem.Header = "Remove selection";
menuItem.Command = new DelegateCommand(() => { this.SelectedItem = null; });
this.ContextMenu = new ContextMenu();
this.ContextMenu.Items.Add(menuItem);
}
void NullableComboBox_KeyUp(object sender, System.Windows.Input.KeyEventArgs e)
{
if (e.Key == Key.Escape || e.Key == Key.Delete)
{
this.SelectedItem = null;
}
}
}
Edit Just noticed Florian GI's comment, the Context Menu might be another good deselect hook to add.
One option would be to create an adapter collection that you expose specifically for consumers that want an initial 'empty' element. You would need to create a wrapper class that implements IList (if you want same performance as with ObservableCollection) and INotifyCollectionChanged. You would need to listen to INotifyCollectionChanged on the wrapped collection, then rebroadcast the events with indices shifted up by one. All of the relevant list methods would also need to shift indices by one.
public sealed class FirstEmptyAdapter<T> : IList<T>, IList, INotifyCollectionChanged
{
public FirstEmptyCollection(ObservableCollection<T> wrapped)
{
}
//Lots of adapter code goes here...
}
Bare minimum if you want to avoid the IList methods is to implement INotifyCollectionChanged and IEnumerable<T>.
One simple approach is to re-template the ComboBox so that when there is an item select a small X appears on the right side of the box. Clicking that clears out the selected item.
This has the advantage of not making your ViewModels any more complicated

WPF ComboBox SelectedItem Set to Null on TabControl Switch

I've got a simple problem in my WPF application which has me banging my head on the table. I have a TabControl, where every TabItem is a View generated for a ViewModel using a DataTemplate similar to this:
<DataTemplate DataType="{x:Type vm:FooViewModel}">
<vw:FooView/>
</DataTemplate>
FooView contains a ComboBox:
<ComboBox ItemsSource="{Binding Path=BarList}" DisplayMemberPath="Name" SelectedItem="{Binding Path=SelectedBar}"/>
and FooViewModel contains a simple Property: public Bar SelectedBar { get; set; }. My problem is that when I set the value for my ComboBox, change to another tab, then change back, the ComboBox is empty again. If I set a breakpoint on the setter for my property, I see that the property is assigned to null when I switch to another tab.
From what I understand, when a tab is switched, it is removed from the VisualTree - but why is it setting my ViewModel's property to null? This is making it very difficult for me to hold persistent state, and checking value != null does not seem like the right solution. Can anyone shed some like on this situation?
Edit: The call stack at the setter breakpoint only shows [External Code] - no hints there.
We just ran into the same problem. We found a blog entry describing the problem. It looks like it is a BUG in WPF and there is a workaround:
Specify the SelectedItem binding before the ItemsSource binding and the problem should be gone.
blog article
TLDR;
Change:
<ComboBox ItemsSource="{Binding Countries, Mode=OneWay}"
SelectedItem="{Binding SelectedCountry}"
DisplayMemberPath="Name" >
</ComboBox>
To:
<ComboBox SelectedItem="{Binding SelectedCountry}"
ItemsSource="{Binding Countries, Mode=OneWay}"
DisplayMemberPath="Name" >
</ComboBox>
My app is using avalondock & prims and had that exact problem. I has same thought with BSG, when we switched tab or document content in MVVM app, the controls as listview+box, combobox is removed from VisualTree. I bugged and saw most data of them was reset to null such as itemssource, selecteditem, .. but selectedboxitem was still hold current value.
A approach is in model, check its value is null then return like this:
private Employee _selectedEmployee;
public Employee SelectedEmployee
{
get { return _selectedEmployee; }
set
{
if (_selectedEmployee == value ||
IsAdding ||
(value == null && Employees.Count > 0))
{
return;
}
_selectedEmployee = value;
OnPropertyChanged(() => SelectedEmployee);
}
But this approach can only solve quite good in first binding level. i mean,
how we go if want to bind SelectedEmployee.Office to combobox, do same is not good
if check in propertyChanged event of SelectedEmployee model.
Basically, we dont want its value is reset null, keep its pre-value. I found a new solution
consistently. By using attached property, i created KeepSelection a-Pro, bool type, for Selector controls, thus supply all its inherited suck as listview, combobox...
public class SelectorBehavior
{
public static bool GetKeepSelection(DependencyObject obj)
{
return (bool)obj.GetValue(KeepSelectionProperty);
}
public static void SetKeepSelection(DependencyObject obj, bool value)
{
obj.SetValue(KeepSelectionProperty, value);
}
// Using a DependencyProperty as the backing store for KeepSelection. This enables animation, styling, binding, etc...
public static readonly DependencyProperty KeepSelectionProperty =
DependencyProperty.RegisterAttached("KeepSelection", typeof(bool), typeof(SelectorBehavior),
new UIPropertyMetadata(false, new PropertyChangedCallback(onKeepSelectionChanged)));
static void onKeepSelectionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var selector = d as Selector;
var value = (bool)e.NewValue;
if (value)
{
selector.SelectionChanged += selector_SelectionChanged;
}
else
{
selector.SelectionChanged -= selector_SelectionChanged;
}
}
static void selector_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var selector = sender as Selector;
if (e.RemovedItems.Count > 0)
{
var deselectedItem = e.RemovedItems[0];
if (selector.SelectedItem == null)
{
selector.SelectedItem = deselectedItem;
e.Handled = true;
}
}
}
}
Final, i use this approach simply in xaml:
<ComboBox lsControl:SelectorBehavior.KeepSelection="true"
ItemsSource="{Binding Offices}"
SelectedItem="{Binding SelectedEmployee.Office}"
SelectedValuePath="Id"
DisplayMemberPath="Name"></ComboBox>
But, selecteditem will never null if selector's itemssource has items. It may affect
some special context.
Hope that helps.
Happy conding! :D
longsam
I had this same problem when scrolling through a virtualizing DataGrid that contains ComboBoxes. Using IsSynchronizedWithCurrentItem did not work, nor did changing the order of the SelectedItem and ItemsSource bindings. But here is an ugly hack that seems to work:
First, give your ComboBox an x:Name. This should be in the XAML for a control with a single ComboBox. For example:
<ComboBox x:Name="mComboBox" SelectedItem="{Binding SelectedTarget.WritableData, Mode=TwoWay}">
Then add these two event handlers in your codebehind:
using System.Windows.Controls;
using System.Windows;
namespace SATS.FileParsing.UserLogic
{
public partial class VariableTargetSelector : UserControl
{
public VariableTargetSelector()
{
InitializeComponent();
mComboBox.DataContextChanged += mComboBox_DataContextChanged;
mComboBox.SelectionChanged += mComboBox_SelectionChanged;
}
/// <summary>
/// Without this, if you grab the scrollbar and frantically scroll around, some ComboBoxes get their SelectedItem set to null.
/// Don't ask me why.
/// </summary>
void mComboBox_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
mComboBox.GetBindingExpression(ComboBox.SelectedItemProperty).UpdateTarget();
}
/// <summary>
/// Without this, picking a new item in the dropdown does not update IVariablePair.SelectedTarget.WritableData.
/// Don't ask me why.
/// </summary>
void mComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
mComboBox.GetBindingExpression(ComboBox.SelectedItemProperty).UpdateSource();
}
}
}
Generally, I use SelectedValue instead of SelectedItem. If I need the object associated with the SelectedValue then I add a lookup field containing this to the target object (as I use T4 templates to gen my viewmodels this tends to be in a partial class). If you use a nullable property to store the SelectedValue then you will experience the problem described above, however if binding the SelectedValue to a non-nullable value (such as an int) then the WPF binding engine will discard the null value as being inappropriate for the target.
Edit:
Below stuff works (I hope...); I developed it because I followed the SelectedItems route described on the MVVM Lite page. However - why do I want to rely on SelectedItems? Adding an IsSelected property to my Items (as shown here) automatically preserves selected items (short of the mentioned cavet in above link). In the end, much easier!
Inital Post:
ok - that was a piece of work; I've a multi-column ListView with SelectionMode="Extension", which makes the whole thing fairly complex. My starting point is invoking tabItems from workspaces similar as describe here.
I made sure that in my ViewModel, I know when a tab item (workspace) is active. (This is a bit similar to here) - of course, somebody needs initalize SelectedWorkspace first.
private Int32 _selectedWorkspace;
public Int32 SelectedWorkspace {
get { return _selectedWorkspace; }
set {
_selectedWorkspace = value;
base.OnPropertyChanged("SelectedWorkspace");
}
}
protected Int32 _thisWorkspaceIdx = -1;
protected Int32 _oldSelectedWorkspace = -1;
public void OnSelectedWorkspaceChanged(object sender, PropertyChangedEventArgs e) {
if (e.PropertyName == "SelectedWorkspace") {
if (_oldSelectedWorkspace >= 0) {
Workspaces[_oldSelectedWorkpace].OnIsActivatedChanged(false);
}
Workspaces[SelectedWorkspace].OnIsActivatedChanged(true);
_oldSelectedWorkspace = SelectedWorkspace;
}
}
protected bool _isActive = false;
protected virtual void OnIsActivatedChanged(bool isActive) {
_isActive = isActive;
}
This allowed me to update the ViewModel selected items only if the tab item (workspace) was actually active. Hence, my ViewModel selected items list is preserved even as the tab item clears the ListView.SelectedItems. In the ViewModel:
if (_isActive) {
// ... update ViewModel selected items, referred below as vm.selectedItems
}
Last, when the tabItem got re-enabled, I hooked up to the 'Loaded' event and restored the SelectedItems. This is done in the code-behind of the View. (Note that whilst my ListView has multiple columns, one serves as a key, the others are for information only. the ViewModel selectedItems list only keeps the key. Else, the comparison below would be more complex):
private void myList_Loaded(object sender, RoutedEventArgs e) {
myViewModel vm = DataContext as myViewModel;
if (vm.selectedItems.Count > 0) {
foreach (string myKey in vm.selectedItems) {
foreach (var item in myList.Items) {
MyViewModel.MyItem i = item as MyViewModel.MyItem;
if (i.Key == myKey) {
myList.SelectedItems.Add(item);
}
}
}
}
}
if you suing async selection in WPF then remove it IsSynchronizedWithCurrentItem="True" from for the ComboBox, please refer to the document about IsSynchronizedWithCurrentItem:
<ComboBox
Name="tmpName"
Grid.Row="10"
Width="250"
Text="Best Match Position List"
HorizontalAlignment="Left"
Margin="14,0,0,0"
SelectedItem="{Binding Path=selectedSurceList,Mode=TwoWay}"
ItemsSource="{Binding Path=abcList}"
DisplayMemberPath="Name"
SelectedValuePath="Code"
IsEnabled="{Binding ElementName=UserBestMatchYesRadioBtn,Path=IsChecked}">
</ComboBox>
also takecare the binding
first use SelectedItem
then ItemsSource
ref:
http://social.msdn.microsoft.com/Forums/vstudio/en-US/fb8a8ad2-83c1-43df-b3c9-61353979d3d7/comboboxselectedvalue-is-lost-when-itemssource-is-updated?forum=wpf
http://social.msdn.microsoft.com/Forums/en-US/c9e62ad7-926e-4612-8b0c-cc75fbd160fd/bug-in-wpf-combobox-data-binding
I solve my problem using the above
I once had a similar problem. It seems that the combobox looses the selected item in VisibilityChanged event. Workarround is to clear the binding before this occurs, and reset it when coming back. You can also try to set the Binding to Mode=TwoWay
Hope that this helps
Jan
I had the same problem and solved it with the following method attached to the Combobox DataContextChanged-Event:
private void myCombobox_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (sender is FrameworkElement && e.NewValue == null)
((FrameworkElement)sender).DataContext = e.OldValue;
}
So everytime you want to remove the datacontext from the combobox, the old datacontext will be set again.
Everytime you change the active Tab of your TabControl, the Combobox will removed from your VisualTree and added if you go back to the one with your combobox. If the combo box is removed from the VisualTree, also the DataContext is set to null.
Or you use a class, which have implemented such feature:
public class MyCombobox : ComboBox
{
public MyCombobox()
{
this.DataContextChanged += MyCombobox_DataContextChanged;
}
void MyCombobox_DataContextChanged(object sender, System.Windows.DependencyPropertyChangedEventArgs e)
{
if (sender is FrameworkElement && e.NewValue == null)
((FrameworkElement)sender).DataContext = e.OldValue;
}
public void SetDataContextExplicit(object dataContext)
{
lock(this.DataContext)
{
this.DataContextChanged -= MyCombobox_DataContextChanged;
this.DataContext = dataContext;
this.DataContextChanged += MyCombobox_DataContextChanged;
}
}
}
I think the problem may be that you arent telling the Combo box when to bind back to the source. Try this:
<ComboBox ItemsSource="{Binding Path=BarList}" DisplayMemberPath="Name" SelectedItem="{Binding Path=SelectedBar, UpdateSourceTrigger=PropertyChanged}"/
You can use the MVVM framework Catel and the catel:TabControl element there this problem is already solved.
Just don't allow your ViewModel's property to be changed if value becomes null.
public Bar SelectedBar
{
get { return barSelected; }
set { if (value != null) SetProperty(ref barSelected, value); }
}
That's it.

Wpf Mvvm ComboBox

I am new in the Wpf world, so I created a couple of views and all of them have at least one ComboBox, as I am using the MvvM pattern, I get my self re-typing all the time the same line of codes to fill the Combo and to get the SelectedItem (creating properties, privates for fill and other to get).
Is there some kind of framework that can improve this part ? or hack/trick ??? as I see too much repetitive code... maybe I am doing something wrong, take a look:
XAML:
<ComboBox name= "cbDepartments" DisplayMemberPath="DepartmentName"
SelectedValuePath ="PrimaryKey"
ItemsSource="{Binding Path=Departments}"
SelectedItem="{Binding Path=DefaultBranch,Mode=TwoWay}"
>
ViewModel:
private Department defaultBranch;
public Department DefaultBranch
{
get
{
return this.defaultBranch;
}
set
{
if (this.defaultBranch != value)
{
this.defaultBranch = value;
this.OnPropertyChanged("DefaultBranch");
this.saveChangeCommand.RaiseCanExecuteChanged();
this.UserMessage = string.Empty;
}
}
}
private ObservableCollection<Department> departments;
public ObservableCollection<Department> Departments
{
get { return this.departments; }
set
{
if (this. departments!= value)
{
this. departments = value;
this.OnPropertyChanged("Departments");
}
}
}
Most of what you have looks pretty standard. There are a few things you can cut down:
It looks like you aren't using SelectedValue so you can remove SelectedValuePath
SelectedItem is TwoWay by default so you can remove the Mode=TwoWay from that binding
For the departments property you should be able to remove the setter entirely and instead add and remove items in the existing collection. This can also help to avoid issues with ItemsSource bindings not getting correct notifications - INotifyCollectionChanged works more consistently that INotifyPropertyChanged on the collection property. Departments could collapse down to:
public ObservableCollection<Department> Departments { get; private set; }
As for making a custom control for the combobox with departments - that is really easy in WPF:
<ComboBox DisplayMemberPath="DepartmentName" x:Class="...DepartmentComboBox"
SelectedValuePath ="PrimaryKey"
ItemsSource="{Binding Path=Departments}"
SelectedItem="{Binding Path=DefaultBranch,Mode=TwoWay}"/>
And Code-behind:
public partial class DepartmentComboBox
{
public DepartmentComboBox()
{
InitializeComponent();
}
}

Resources