I want to create editable combo box with the following properties:
Bind the text property to my data model.
The data model may override the changes in the GUI, even in Selection changed. E.g. I can choose from 1, 2, 3 I choose 2, but some component down under changes it to 3.
Update the data model on the following events:
Selection Changed
Lose focus
Enter pressed (should behave the same as lost focus).
I've been able to create such control, but it's pretty ugly (using many hacks) and I hoped there's a simpler way…
Thanks in advance
Ok, I here's what I've done, and its not that ugly:
/// <summary>
/// Editable combo box which updates the data model on the following:
/// 1. Select## Heading ##ion changed
/// 2. Lost focus
/// 3. Enter or Return pressed
///
/// In order for this to work, the EditableComboBox requires the follows, when binding:
/// The data model value should be bounded to the Text property of the ComboBox
/// The binding expression UpdateSourceTrigger property should be set to LostFocus
/// e.g. in XAML:
/// <PmsEditableComboBox Text="{Binding Path=MyValue, UpdateSourceTrigger=LostFocus}"
/// ItemsSource="{Binding Path=MyMenu}"/>
/// </summary>
public class PmsEditableComboBox : ComboBox
{
/// <summary>
/// Initializes a new instance of the <see cref="PmsEditableComboBox"/> class.
/// </summary>
public PmsEditableComboBox()
: base()
{
// When TextSearch enabled we'll get some unwanted behaviour when typing
// (i.e. the content is taken from the DropDown instead from the text)
IsTextSearchEnabled = false;
IsEditable = true;
}
/// <summary>
/// Use KeyUp and not KeyDown because when the DropDown is opened and Enter is pressed
/// We'll get only KeyUp event
/// </summary>
protected override void OnKeyUp(KeyEventArgs e)
{
base.OnKeyUp(e);
// Update binding source on Enter
if (e.Key == Key.Return || e.Key == Key.Enter)
{
UpdateDataSource();
}
}
/// <summary>
/// The Text property binding will be updated when selection changes
/// </summary>
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
base.OnSelectionChanged(e);
UpdateDataSource();
}
/// <summary>
/// Updates the data source.
/// </summary>
private void UpdateDataSource()
{
BindingExpression expression = GetBindingExpression(ComboBox.TextProperty);
if (expression != null)
{
expression.UpdateSource();
}
}
}
The simplest way to do this is to use the UpdateSourceTrigger property on the binding. You may not be able to match your current behavior exactly, but you may find that it is comparable.
The UpdateSourceTrigger property controls when the target of the binding updates the source. Different WPF controls have different default values for this property when bound.
Here are your options:
UpdateSourceTrigger.Default = Allow target control to determine UpdateSourceTrigger mode.
UpdateSourceTrigger.Explicit = Only update source when someone calls BindingExpression.UpdateSource();
UpdateSourceTrigger.LostFocus = Automatically update binding source whenever the target looses focus. This way a change can be completed, and then the binding is updated after the user moves on.
UpdateSourceTrigger.PropertyChanged = Whenever the DependencyProperty on the target changes values the source is updated immediatly. Most UserControls do not default to this property because it requires more binding updates (can be a performance issue).
Related
I have the following situation:
I have a User Control with just a single Grid inside.
The Grid has its first column as a checkbox column, which is bound to the IsSelected property of CustomerModel
ItemsSource for the Grid is bound to List< CustomerModel>
When the user checks any of the CheckBoxes the corresponding IsSelected property of CustomerModel is getting updated
Query:
I added a dependency property to the UserControl named "SelectedCustomerItems", and I want it to return a List< CustomerModel> (Only for IsSelected = true)
This UserControl is placed on another Window
The Dependency Property "SelectedCustomerItems" is bound to "SelectedCustomers" property inside the WindowViewModel
But I am not getting the SelectedCustomer Items through this dependency property. Breakpoint is not hitting in Get{} Please suggest....
Implement your DPs this way:
#region SomeProperty
/// <summary>
/// The <see cref="DependencyProperty"/> for <see cref="SomeProperty"/>.
/// </summary>
public static readonly DependencyProperty SomePropertyProperty =
DependencyProperty.Register(
SomePropertyPropertyName,
typeof(object),
typeof(SomeType),
// other types here may be appropriate for your situ
new FrameworkPropertyMetadata(null, OnSomePropertyPropertyChanged));
/// <summary>
/// Called when the value of <see cref="SomePropertyProperty"/> changes on a given instance of <see cref="SomeType"/>.
/// </summary>
/// <param name="d">The instance on which the property changed.</param>
/// <param name="e">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
private static void OnSomePropertyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as SomeType).OnSomePropertyChanged(e.OldValue as object, e.NewValue as object);
}
/// <summary>
/// Called when <see cref="SomeProperty"/> changes.
/// </summary>
/// <param name="oldValue">The old value</param>
/// <param name="newValue">The new value</param>
private void OnSomePropertyChanged(object oldValue, object newValue)
{
}
/// <summary>
/// The name of the <see cref="SomeProperty"/> <see cref="DependencyProperty"/>.
/// </summary>
public const string SomePropertyPropertyName = "SomeProperty";
/// <summary>
///
/// </summary>
public object SomeProperty
{
get { return (object)GetValue(SomePropertyProperty); }
set { SetValue(SomePropertyProperty, value); }
}
#endregion
You must understand that a DependencyProperty isn't just a property with a bunch of junk added, its a hook into the WPF Binding system. This is a vast, complex system that lives below the sea level upon which your DP daintily floats. It behaves in ways that you will not expect, unless you actually learn it.
You are experiencing the first revelation all of us had with DPs: Bindings do NOT access DependencyProperty values via the property accessors (i.e., get and set methods). These property accessors are convenience methods for you to use from code only. You could dispense with them and use DependencyObject.GetValue and DependencyObject.SetValue, which are the actual hooks into the system (see the implementation of the getters/setters in my example above).
If you want to listen for change notification, you should do what I have done above in my example. You can add a change notification listener when registering your DependencyProperty. You can also override the "Metadata" of inherited DependencyProperties (I do this all the time for DataContext) in order to add change notification (some use the DependencyPropertyDescriptor for this, but I've found them to be lacking).
But, whatever you do, do NOT add code to the get and set methods of your DependencyProperties! They won't be executed by binding operations.
For more information about how DependencyProperties work, I highly suggest reading this great overview on MSDN.
I have a ComboBox that has its ItemSource and SelectedItem properties bound to a view model. And I have the following block of code that is the callback for a data query against a DomainContext:
/// <summary>
/// Stores (readonly) - Stores available for ship to store.
/// </summary>
public ObservableCollection<StoreEntity> Stores
{
get { return _stores; }
private set { _stores = value; RaisePropertyChanged("Stores"); }
}
/// <summary>
/// SelectedStore - Currently selected store.
/// </summary>
public StoreEntity SelectedStore
{
get { return _selectedStore; }
set { _selectedStore = value; RaisePropertyChanged("SelectedStore"); }
}
/// <summary>
/// When stores are completely loaded.
/// </summary>
/// <param name="a_loadOperations"></param>
protected void OnStoresLoaded(LoadOperation<StoreEntity> a_loadOperations)
{
Stores.AddRange(a_loadOperations.Entities);
SelectedStore = a_loadOperations.Entities.FirstOrDefault();
}
In this example, Stores is a ObservableCollection<StoreEntity> (AddRange is an extention method) and is bound to ItemSource, and SelectedStore is a StoreEntity and is bound to SelectedItem.
The problem here is that the ComboBox is not changing its selection to reflect the change in SelectedItem.
Edits:
I've even tried the following, though I think that a_loadOperation.Entities is already a realized set:
/// <summary>
/// When stores are completely loaded.
/// </summary>
/// <param name="a_loadOperations"></param>
protected void OnStoresLoaded(LoadOperation<StoreEntity> a_loadOperations)
{
var entities = a_loadOperations.Entities.ToArray();
Stores.AddRange(entities);
SelectedStore = entities.First();
}
Thanks
If you are trying to get a change to your viewmodel (specifically the SelectedStore property) to be reflected in your combo box, you could:
Confirm the binding worked. Check it was set up properly in the XAML, and check the Output window to see if there is a message saying the Binding failed
Confirm your DataContext is set properly (it probably is since you are getting the combo box `ItemsSource` from `Stores`
Subscribe to your PropertyChanged event and confirm that it is being raised when the property changes
If that doesn't work, it may be a bug with the ComboBox. I have seen cases where the order of specifying the properties in XAML makes the difference (ex: you should set ItemsSource first and SelectedItem second). I have also seen a binding fail until I added Mode=TwoWay (even though in your example you are trying to get the binding to update from your view model to your UI). Try confirming that your ComboBox XAML is like this:
<ComboBox ItemsSource="{Binding Stores}" SelectedItem="{Binding SelectedStore, Mode=TwoWay}" />
Order shouldn't matter since XAML is declarative, but I have personally seen it matter with ComboBoxes in Silverlight.
I had SelectedItem bound to Stores instead of SelectedStore. Ooops!
I plan to create a typical Master-Detail scenario, i.e. a collection of items displayed in a ListView via DataBinding to an ICollectionView, and details about the selected item in a separate group of controls (TextBoxes, NumUpDowns...).
No problem so far, actually I have already implemented a pretty similar scenario in an older project. However, it should be possible to select multiple items in the ListView and get the appropriate shared values displayed in the detail view. This means, if all selected items have the same value for a property, this value should be displayed in the detail view. If they do not share the same value, the corresponding control should provide some visual clue for the user indicating this, and no value should be displayed (or an "undefined" state in a CheckBox for example). Now, if the user edits the value, this change should be applied to all selected items.
Further requirements are:
MVVM compatibility (i.e. not too much code-behind)
Extendability (new properties/types can be added later on)
Does anyone have experience with such a scenario? Actually, I think this should be a very common scenario. However, I could not find any details on that topic anywhere.
Thanks!
gehho.
PS: In the older project mentioned above, I had a solution using a subclass of the ViewModel which handles the special case of multi-selection. It checked all selected items for equality and returned the appropriate values. However, this approach had some drawbacks and somehow seemed like a hack because (besides other smelly things) it was necessary to break the synchronization between the ListView and the detail view and handle it manually.
In your ViewModel, create a property that will bind to your ListView's SelectedItems.
Create another property that will represent the details object of your selected items.
The details section (in the XAML) is binding to this details property (in the ViewModel).
Every time selected items collection is modified (setter/CollectionChanged event), you need to update your details object as well (that will iterate through the relevant properties and decide if they're of the same value or not).
Once a property in the details object is modified, you need to iterate back on your selected items collection and make the relevant changes on them.
That's it.
Hope it helps
I am running into this same exact issue.
IsSynchronizedWithCurrentItem = "True" only keeps the CurrentItem in sync (the last item you selected without holding ctrl or shift).
Just as you probably have, I have searched the internet far and wide for an answer and never came up with one. My scenario has a 3 tier Master>Detail>Detail binding, where each tier is bound to it's own ListBox.
I've rigged up something that works for the time being.
For my Master>Detail>Detail tiers I created an individual CollectionViewSource for each tier and set that CollectionViewSource.Source to the appropriate entity object. On the MasterView bound ListBox's SelectionChanged Event I performed a filter on the MasterView.View to retrieve objects where the Master Primary Key = Detail Foreign Key.
It's sloppy, but if you have found a better way to get this done, I'd love to hear it.
I have used an approach similar to the one suggested by Captain. I have created one property in my ViewModel which represents multiple items (i.e. all selected items). In the property get- and set-accessors I have used the following methods to determine/set the shared values of/for all items. This approach does not use any reflection, but uses delegates in the form of lambda expressions which makes it pretty fast.
As an overview, this is my basic design:
public class MyMultiSelectionViewModel
{
private List<MyItemType> m_selectedItems = new List<MyItemType>();
public void UpdateSelectedItems(IList<MyItemType> selectedItems)
{
m_selectedItems = selectedItems;
this.OnPropertyChanged(() => this.MyProperty1);
this.OnPropertyChanged(() => this.MyProperty2);
// and so on for all relevant properties
}
// properties using SharedValueHelper (see example below)
}
The properties look like this:
public string Name
{
get
{
return SharedValueHelper.GetSharedValue<MyItemType, string>(m_selectedItems, (item) => item.Name, String.Empty);
}
set
{
SharedValueHelper.SetSharedValue<MyItemType, string>(m_selectedItems, (item, newValue) => item.Name = newValue, value);
this.OnPropertyChanged(() => this.Name);
}
}
And the code for the SharedValueHelper class looks like this:
/// <summary>
/// This static class provides some methods which can be used to
/// retrieve a <i>shared value</i> for a list of items. Shared value
/// means a value which represents a common property value for all
/// items. If all items have the same property value, this value is
/// the shared value. If they do not, a specified <i>non-shared value</i>
/// is used.
/// </summary>
public static class SharedValueHelper
{
#region Methods
#region GetSharedValue<TItem, TProperty>(IList<TItem> items, Func<TItem, TProperty> getPropertyDelegate, TProperty nonSharedValue)
/// <summary>
/// Gets a value for a certain property which represents a
/// <i>shared</i> value for all <typeparamref name="TItem"/>
/// instances in <paramref name="items"/>.<br/>
/// This means, if all wrapped <typeparamref name="TItem"/> instances
/// have the same value for the specific property, this value will
/// be returned. If the values differ, <paramref name="nonSharedValue"/>
/// will be returned.
/// </summary>
/// <typeparam name="TItem">The type of the items for which a shared
/// property value is requested.</typeparam>
/// <typeparam name="TProperty">The type of the property for which
/// a shared value is requested.</typeparam>
/// <param name="items">The collection of <typeparamref name="TItem"/>
/// instances for which a shared value is requested.</param>
/// <param name="getPropertyDelegate">A delegate which returns the
/// property value for the requested property. This is used, so that
/// reflection can be avoided for performance reasons. The easiest way
/// is to provide a lambda expression like this:<br/>
/// <code>(item) => item.MyProperty</code><br/>
/// This expression will simply return the value of the
/// <c>MyProperty</c> property of the passed item.</param>
/// <param name="nonSharedValue">The value which should be returned if
/// the values are not equal for all items.</param>
/// <returns>If all <typeparamref name="TItem"/> instances have
/// the same value for the specific property, this value will
/// be returned. If the values differ, <paramref name="nonSharedValue"/>
/// will be returned.</returns>
public static TProperty GetSharedValue<TItem, TProperty>(IList<TItem> items, Func<TItem, TProperty> getPropertyDelegate, TProperty nonSharedValue)
{
if (items == null || items.Count == 0)
return nonSharedValue;
TProperty sharedValue = getPropertyDelegate(items[0]);
for (int i = 1; i < items.Count; i++)
{
TItem currentItem = items[i];
TProperty currentValue = getPropertyDelegate(currentItem);
if (!sharedValue.Equals(currentValue))
return nonSharedValue;
}
return sharedValue;
}
#endregion
#region SetSharedValue<TItem, TProperty>(IList<TItem> a_items, Action<TItem, TProperty> a_setPropertyDelegate, TProperty a_newValue)
/// <summary>
/// Sets the same value for all <typeparamref name="TItem"/>
/// instances in <paramref name="a_items"/>.
/// </summary>
/// <typeparam name="TItem">The type of the items for which a shared
/// property value is requested.</typeparam>
/// <typeparam name="TProperty">The type of the property for which
/// a shared value is requested.</typeparam>
/// <param name="items">The collection of <typeparamref name="TItem"/>
/// instances for which a shared value should be set.</param>
/// <param name="setPropertyDelegate">A delegate which sets the
/// property value for the requested property. This is used, so that
/// reflection can be avoided for performance reasons. The easiest way
/// is to provide a lambda expression like this:<br/>
/// <code>(item, newValue) => item.MyProperty = newValue</code><br/>
/// This expression will simply set the value of the
/// <c>MyProperty</c> property of the passed item to <c>newValue</c>.</param>
/// <param name="newValue">The new value for the property.</param>
public static void SetSharedValue<TItem, TProperty>(IList<TItem> items, Action<TItem, TProperty> setPropertyDelegate, TProperty newValue)
{
if (items == null || items.Count == 0)
return;
foreach (TItem item in items)
{
try
{
setPropertyDelegate(item, newValue);
}
catch (Exception ex)
{
// log/error message here
}
}
}
#endregion
#endregion
}
Hi a quick question really in WPF.
Does anyone know how i can bind a combo box that will sit in a grid to a dataset that i have.
The dataset contains several columns and numerious rows but i want to take one of the columns, lets say ProcessID and just show that in the comobo box.
Has anyone done this before or know how it can be done in WPF.
Thanks
Iffy.
Assuming this is a capital D DataSet that you can expose to XAML as a property on a DataContext object (showing the "Description" column for each row in the "LookupTable" table):
<ComboBox ItemsSource="{Binding Path=MyDataSetExposedAsAProperty.Tables[LookupTable].Rows}"
DisplayMemberPath=".[Description]"/>
To do it programmatically, here's a generic function I have for it:
/// <summary>
/// Thread safe method for databinding the specified ComboBox with the specified data.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="ctrl">The Combo to be databound.</param>
/// <param name="datasource">The data to be bound to the Combo.</param>
/// <param name="displayPath">The name of the property in the data objects that should be used for the display value.</param>
/// <param name="valuePath">The name of the property in the data objects that should be used for the value.</param>
/// <remarks>
/// This function was written as generic so that it doesn't matter what types the IEnumerabe datasource is, it can handle them all.
/// </remarks>
private void UpdateComboDataSource<T>(ComboBox ctrl, IEnumerable<T> datasource, string displayPath, string valuePath)
{
if (ctrl == null)
throw new ArgumentNullException("Control to be bound must not be null");
//check if we are on the control's UI thread:
if (ctrl.Dispatcher.CheckAccess())
{
//we have full access to the control, no threading issues, so let's rip it up and databind it
datasource = datasource.OrderBy(x => x);
if (displayPath != null && ctrl.DisplayMemberPath == null)
ctrl.DisplayMemberPath = displayPath;
if (valuePath != null && ctrl.SelectedValuePath == null)
ctrl.SelectedValuePath = valuePath;
ctrl.ItemsSource = datasource;
//be nice to the user, if there is only one item then automatically select it
ctrl.SelectedIndex = datasource.Count() == 1 ? 0 : -1;
}
else
{
//we don't have full access to the control because we are running on a different thread, so
//we need to marshal a call to this function from the control's UI thread
UpdateComboDataSourceDelegate<T> del = new UpdateComboDataSourceDelegate<T>(this.UpdateComboDataSource);
ctrl.Dispatcher.BeginInvoke(del, ctrl, datasource, displayPath, valuePath);
}
}
private delegate void UpdateComboDataSourceDelegate<T>(ComboBox ctrl, IEnumerable<T> dataSource, string displayPath, string valuePath);
The comments tell you everything you need to know. It could also be done programmatically by creating a Binding between the control's ItemSource property and a public property on your page - this is the same way as it is done declaratively (there must be a billion examples of that out there though, so I won't show it here).
You should use LINQ to SQL,ADO.Net, or entity framework to extract data from database, you can't bind combobox with database table, but whit data in the memory
i have a mvvm app that the main window is a tab control.
i use the itemssource to bind items to the combo box,
everything works fine until i go to another tab and for some reason the selected item of the combo box getting the null value, any ideas ?
the binding is twoway updatesource onpropertychanged and the property is type of observablecollection
I had same problem before. And the solution is that make sure Itemsource attribute of comboBox in XAML has not been declared before SelectedValue attribute. It should work then.
It is just necessary to work on a ObservableCollection and to load only as AddRange (not use 'Add')
When we load the data as:
foreach (object item in items)
{
MyList.Add(item); // Where MyList is a ObservableCollection
}
Then after adding of the first element the ObservableCollection call OnCollectionChanged.
Then the ComboBox will try to select the SelectedValue and return 'null' when can't find this element.
It can solve a problem:
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
namespace System.Collections.ObjectModel
{
/// <summary>
/// Represents a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed.
/// </summary>
/// <typeparam name="T"></typeparam>
public class ObservableCollectionEx<T> : ObservableCollection<T>
{
/// <summary>
/// Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class.
/// </summary>
public ObservableCollectionEx()
: base() { }
/// <summary>
/// Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class that contains elements copied from the specified collection.
/// </summary>
/// <param name="collection">collection: The collection from which the elements are copied.</param>
/// <exception cref="System.ArgumentNullException">The collection parameter cannot be null.</exception>
public ObservableCollectionEx(IEnumerable<T> collection)
: base(collection) { }
/// <summary>
/// Adds the elements of the specified collection to the end of the ObservableCollection(Of T).
/// </summary>
public void AddRange(IEnumerable<T> collection)
{
//
// Add the items directly to the inner collection
//
foreach (var data in collection)
{
this.Items.Add(data);
}
//
// Now raise the changed events
//
this.OnPropertyChanged(new PropertyChangedEventArgs("Count"));
this.OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
}
I have an mvvm app with almost exactly the same scenario. The main window has a tab control. There is a tab containing a combobox. The combobox itemsource is bound to an IList (in the view model) and the Selected value is bound to a property in the view model implementing INotifyPropertyChanged.
<ComboBox ItemsSource="{Binding AllowedJudges}"
SelectedValue="{Binding SelectedJudge, UpdateSourceTrigger=PropertyChanged}" >
When selecting another tab, the view model's property bound to the SelectedValue mysteriously gets set to null. I'm able to handle it by not allowing the SelectedValue-bound property to be set to null:
public Judge SelectedJudge
{
get { return selectedJudge; }
set
{
if(selectedJudge==value || value==null) return;
selectedJudge = value;
OnPropertyChanged("SelectedJudge");
updateViewData();
}
}
However, it's not clear to me why a tab pane becoming invisible implies a value in a combobox there becomes deselected....
If for some Reason the BindingSource of the ItemsSource does no longer contain the SeletedItem (because it's re-initialized or whatever), then the SelectedItem can be reset to default, i.e. null.
From your example I can't tell why this should happen, but that maybe because you just missed adding some more environmental code.
It`s may be problem of your viewmodel ierarchy.
For example, if both ends of your Binding are dependency properties and ownertype property is not tied to a particular class (for example, set the parent class), this dependency property will be used by all the inheritors together. Bad design