Databinding a WinForms ComboBox requires user-interaction twice - winforms

I have a ComboBox (DropDownList style) in a Windows Form which has a data-source set to a BindingList, and has the SelectedValue property bound to a viewmodel's property.
Note that the binding is set to OnPropertyChanged rather than OnValidate, this is because when using OnValidate the control will not necessarily update the ViewModel if the form is closed or loses focus (but the control still thinks it has focus. On the Compact Framework there is no way to 'force validation' so I have to use OnPropertyChanged.
There's a problem which is reproducible on both desktop Windows Forms and Smart Device Windows Forms: when attempting to select or set the current item in the combobox (using the mouse or keyboard) the value will not "stick" until it is set twice - that is, you need to select the same item twice before the combobox's value will change.
There are no exceptions thrown (even caught exceptions) and no diagnostics reports to speak of.
I don't think this is a bug in the framework, and it's interesting how it happens on both Desktop and Compact Framework.
Here's my code:
Form1.cs
public partial class Form1 : Form {
private ViewModel _vm;
public Form1() {
InitializeComponent();
this.bindingSource1.Add( _vm = new ViewModel() );
}
}
Form1.Designer.cs (relevant lines)
//
// bindingSource1
//
this.bindingSource1.DataSource = typeof( WinForms.Shared.ViewModel );
//
// comboBox1
//
this.comboBox1.DataBindings.Add( new System.Windows.Forms.Binding( "SelectedValue", this.bindingSource1, "SelectedSomeTypeId", true, System.Windows.Forms.DataSourceUpdateMode.OnPropertyChanged ) );
this.comboBox1.DataSource = this.someTypeListBindingSource;
this.comboBox1.DisplayMember = "DisplayText";
this.comboBox1.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.comboBox1.FormattingEnabled = true;
this.comboBox1.Location = new System.Drawing.Point( 12, 27 );
this.comboBox1.Name = "comboBox1";
this.comboBox1.Size = new System.Drawing.Size( 182, 21 );
this.comboBox1.TabIndex = 0;
this.comboBox1.ValueMember = "Id";
//
// someTypeListBindingSource
//
this.someTypeListBindingSource.DataMember = "SomeTypeList";
this.someTypeListBindingSource.DataSource = this.bindingSource1;
ViewModel.cs
public class ViewModel : INotifyPropertyChanged {
public ViewModel() {
this.SomeTypeList = new BindingList<SomeType>();
for(int i=0;i<5;i++) {
this.SomeTypeList.Add( new SomeType() {
Id = i + 1,
Name = "Foo" + ((Char)( 'a' + i )).ToString()
} );
}
this.SelectedSomeTypeId = 2;
}
public BindingList<SomeType> SomeTypeList { get; private set; }
private Int64 _selectedSomeTypeId;
public Int64 SelectedSomeTypeId {
get { return _selectedSomeTypeId; }
set {
if( _selectedSomeTypeId != value ) {
_selectedSomeTypeId = value;
OnPropertyChanged("SelectedSomeTypeId");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(String propertyName) {
PropertyChangedEventHandler handler = this.PropertyChanged;
if( handler != null ) handler( this, new PropertyChangedEventArgs(propertyName) );
}
}
public class SomeType {
public String Name { get; set; }
public Int64 Id { get; set; }
public String DisplayText {
get { return String.Format("{0} - {1}", this.Id, this.Name ); }
}
}

I have never found the 'right' way around this issue and generally use one of two ways to make things work:
Direct: Just bypass the binding mechanism for this one entry
combo1.SelectedIndexChanged += (s,e) _viewModel.Item = combo1.SelectedItem;
Generic Binding: Make a custom ComboBox and override the OnSelectedIndexChanged event to force the binding update.
public class BoundComboBox : ComboBox
{
protected override void OnSelectedIndexChanged(EventArgs e)
{
var binding = this.DataBindings["SelectedItem"];
if( binding != null )
binding.WriteValue();
base.OnSelectedIndexChanged(e);
}
}

Related

INotifyDataErrorInfo only trigger when needed

I have implemented the INotifyDataErrorInfo on my models. But I cant seem to use it when needed. For Example. It should not validate on errors on startup or as I am typing. Only when clicking on a button (save).
I have currently this in my XAML:
<TextBox Text="{Binding Car.Model, ValidatesOnNotifyDataErrors=False, UpdateSourceTrigger=Explicit}"/>
And in my ViewModel under the SaveCommand:
Car.Validate();
if (Car.HasErrors)
{
return;
}
//else save
My Model looks like this:
Public class Car:ValidateModelBase
{
private string _model;
[Required (ErrorMessage ="This field is required")]
public string Model
{
get { return _model; }
set { _model= value; RaisePropertyChanged(); }
}
}
And then my implementation of ValidateModelBase:
public class ValidateModelBase: INotifyDataErrorInfo, INotifyPropertyChanged
{
private ConcurrentDictionary<string, List<string>> _errors =
new ConcurrentDictionary<string, List<string>>();
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged([CallerMemberName]string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
ValidateAsync();
}
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public void OnErrorsChanged(string propertyName)
{
var handler = ErrorsChanged;
if (handler != null)
handler(this, new DataErrorsChangedEventArgs(propertyName));
}
public IEnumerable GetErrors(string propertyName)
{
if (string.IsNullOrEmpty(propertyName))
{
return null;
}
List<string> errorsForName;
_errors.TryGetValue(propertyName, out errorsForName);
return errorsForName;
}
public bool HasErrors
{
get { return _errors.Any(kv => kv.Value != null && kv.Value.Count > 0); }
}
public Task ValidateAsync()
{
return Task.Run(() => Validate());
}
private object _lock = new object();
public void Validate()
{
lock (_lock)
{
var validationContext = new ValidationContext(this, null, null);
var validationResults = new List<ValidationResult>();
Validator.TryValidateObject(this, validationContext, validationResults, true);
foreach (var kv in _errors.ToList())
{
if (validationResults.All(r => r.MemberNames.All(m => m != kv.Key)))
{
List<string> outLi;
_errors.TryRemove(kv.Key, out outLi);
OnErrorsChanged(kv.Key);
}
}
var q = from r in validationResults
from m in r.MemberNames
group r by m into g
select g;
foreach (var prop in q)
{
var messages = prop.Select(r => r.ErrorMessage).ToList();
if (_errors.ContainsKey(prop.Key))
{
List<string> outLi;
_errors.TryRemove(prop.Key, out outLi);
}
_errors.TryAdd(prop.Key, messages);
OnErrorsChanged(prop.Key);
}
}
}
}
The thing is its working as it normally should, but the textbox is already red with rquired when Window is opened. I want it to ignore validation on startup and only validate the moment I click Save. When clicking save, it would validate and see if there is any errors. When there is errors, the validation is set (now it should be marked in red) and the window stays open. How can I achieve this.
If your model should only validate when saving, I would have your model implement IEditableObject and ensure that BeginEdit is called prior to changes being made, and call EndEdit prior to committing those changes (i.e., saving).
Have your base class track whether or not it's in 'edit mode', and if it is, suppress any validation. When EndEdit is called, allow validation again and fire ErrorsChanged. It's up to you to decide how to handle CancelEdit.
Some controls, like data grids, have built-in support for IEditableObject and will call BeginEdit when you begin editing a row, and EndEdit when committing a row. It's a pretty useful interface.

OnPropertyChanged wont change when used wth observable collection and single property

Loads the dataGrid and populates the Datagrid a row of 1'
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
update();
//this.DataContext = this;
}
CricketEvent events = new CricketEvent();
private void update()
{
events.updateList(new CricketEvent[1] { new CricketEvent(){Runs="1"} });
DG1.ItemsSource = events.RunsList;
}
private void DG1_SelectedCellsChanged(object sender, SelectedCellsChangedEventArgs e)
{
Window1 windowToOpen = new Window1();
var selectedUser = this.DG1.SelectedItem;
windowToOpen.Show();
}
}
Main class that loads the OnPropertyChanged I have a List property and string property that calls the OnPropertyChanged but I want the individual "Runs" property to be updated on its own rather than the whole collection.
class CricketEvent : INotifyPropertyChanged
{
private ObservableCollection<CricketEvent> runsList;
public string runs { get; set; }
public CricketEvent(string numofRuns) {
this.Runs = numofRuns;
}
public CricketEvent() { }
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<CricketEvent> RunsList
{
get { return this.runsList; }
set
{
if (value != this.runsList)
{
this.runsList = value;
OnPropertyChanged("RunsList");
}
}
}
public string Runs
{
get { return runs; }
set
{
runs = value;
// Call OnPropertyChanged whenever the property is updated
OnPropertyChanged("Runs");
}
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
public ObservableCollection<CricketEvent> updateList(CricketEvent []events)
{
runsList = new ObservableCollection<CricketEvent>(events.ToList());
return runsList;
}
}
This is the update window that brings up a text box and should change the "1s" In the previous window to whatever is typed into the textbox
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
CricketEvent events = new CricketEvent();
MainWindow main = new MainWindow();
private void Button_Click(object sender, RoutedEventArgs e)
{
events.updateList(new CricketEvent[1] { new CricketEvent(txt1.Text.ToString()) });
main.DG1.ItemsSource = events.RunsList;
}
The Button_Click event in Window1 does not use the instance of MainWindow that is show - it creates a new Window instance (that is not shown) and adds the updated list to the DG1.ItemsSource property. To solve that, pass the original instance of Window to the created Window1 in constructor and use that.
However, you should review your update strategy (and code style) because there is potential for improvments:
It is not a good idea to create a new collection if you want to update just one property of one item. Observable collections provide change notification, so you dont have to recreate the collection at all.
Instead of assinging the collection in code behind, use databinding to bind the collection to the ItemsSource. DataBinding results in automatic update of GUI elements if the collection or one item of you collection changed.

MVVM pattern filter listview and update on new item

I've been struggling with this problem for a couple of days, but somewhere I obviously on a wrong track. Situation is as follows: I have a window with 3 buttons (Add New Task, Show Inbox, Show Today) and a Listview. My TaskViewModel class is has a ObservableCollection of TaskModel, with pretty simple Filter functionality. My class looks as follows:
public class TaskViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public ObservableCollection<TaskModel> TaskCollection { get; private set; }
public TaskViewModel()
{
TaskDataAccess ac = new TaskDataAccess();
this.TaskCollection = ac.GetAllTasks();
}
public ICommand AddTaskCommand
{
get { return new DelegateCommand(this.AddTask); }
}
public ICommand FilterInboxCommand
{
get { return new DelegateCommand(this.FilterInbox); }
}
public void AddTask()
{
this.TaskCollection.Add(new TaskModel(9, "I", "New Item for testing"));
this.GetListCollectionView().Filter = this.IsInbox; ;
}
private void FilterInbox()
{
this.GetListCollectionView().Filter = this.IsInbox;
}
....
}
The filter functionality works fine, but when I call the new window "Add new task" it does not update the listview (here: this.TaskCollection.Add(new TaskModel(9, "I", "New Item for testing"));
I'd appreciate if someone could give me a hint...
Try to do this...
create a private field (say _taskCollection) to backup your property TaskCollection.
private readonly ObservableCollection<TaskModel> _taskCollection;
Then remove the private setter from TaskCollection property. Also remove the constructor code that loads the collection.
Instead write your getter this way...
public ObservableCollection<TaskModel> TaskCollection {
get {
if (this._taskCollection == null)
{
TaskDataAccess ac = new TaskDataAccess();
this._taskCollection = ac.GetAllTasks();
}
return this._taskCollection;
}
}
Let me know if this way works ....

silver light datagrid

Iam new to silverlight so i need help from your side. my query is one page haivng the datagrid,that datagrid have only 6 columns.after 6 columns their is a scape so that scape showing itself one column.so i avoid that column in datagrid.scape may be show with out the column this is my query.
it is urgent for me.please resolve the solution as possible as early.
While I have no idea what a 'scape' is, what you need to do is start out by creating a 'display' class that inherits from IEditable and INotify. For example:
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
namespace Application.Views.DisplayClasses
{
public class DisplayClass : IEditableObject, INotifyPropertyChanged
{
//Create private vars
private string a;
private string b;
private string c;
private string d;
private bool e;
// Create public properties with meta data to tell the grid to display and what order etc
[Display(AutoGenerateField = false)]
public string A
{
get { return a; }
set { a = value; }
}
[Display(Order = 0, Name = "B", AutoGenerateField = true)]
public string B
{
get { return b; }
set { b = value; }
}
[Display(Order = 1, Name = "C", AutoGenerateField = true)]
public String C
{
get { return c; }
set { c= value; }
}
[Display(Order = 2, Name = "D", AutoGenerateField = true)]
public string D
{
get { return d; }
set { d = value; }
}
[Display(Order = 2, Name = "E", AutoGenerateField = true)]
public string E
{
get { return e; }
set { e = value; }
}
#region IEditableObject Members
public void BeginEdit()
{
}
public void CancelEdit()
{
}
public void EndEdit()
{
}
#endregion
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
System.ComponentModel.PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
if ((propertyChanged != null))
{
propertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
}
Then you need to create an ObservableCollection to store the data your getting back from the database in:
// Code to get data from the database (from your webservice)
//Make this Observable collection global
public static ObservableCollection<DisplayClass> ItemList = new ObservableCollection<DisplayClass>();
// In your oncompleted event method, put something similar to the following code
foreach (var DatabaseItem in DataFromMyWebService)
{
DisplayClass GridItem = new DisplayClass();
GridItem.A = DatabaseItem.A;
GridItem.B = DatabaseItem.B;
GridItem.C = DatabaseItem.C;
GridItem.D = DatabaseItem.D;
ItemList.Add(GridItem);
}
dgDataGrid.ItemsSource = ItemList;
You want to make your observable collection global so that if you need to change an item in your collection, the datagrid will automatically display those changes. Notice the meta data ([]) in the display class. That is how you control which properties are displayed and in what order. You will also want to set the property 'AutoGenerate="True"' in your datagrid element in your XAML code.

Silverlight: INotifyPropertyChanged does not seem to work

I haven't implement this pattern for a while (and when I did it was in 2, as opposed to 3), and I have several examples that all seem straight forward, but I can't work out what I have done wrong in the below piece of code (The Items are not updated when the property event fires):
public partial class Index : Page
{
private IndexViewModel _vm;
public Index()
{
InitializeComponent();
_vm = new IndexViewModel(19);
this.TheDataGrid.ItemsSource = _vm.Rows;
}
public class IndexViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
this.PropertyChanged(this, e);
}
public SortableCollectionView Rows
{
get
{
return _rows;
}
set
{
if (_rows == value)
return;
_rows = value;
this.OnPropertyChanged(new PropertyChangedEventArgs("Rows"));
}
}
This does not refresh my datagrid... as a 'hack' I have had to pass the datagrid object into my viewmodel and bind it there:
public IndexViewModel(int containerModelId, DataGrid shouldNotNeed)
{
ContainerModelId = containerModelId;
LoadOperation<vwColumn> headings = _ttasContext.Load(_ttasContext.GetRecordColumnsQuery(ContainerModelId));
headings.Completed += (sender2, e2) =>
{
//load data
LoadOperation<vwDataValue> data = _ttasContext.Load(_ttasContext.GetRecordsQuery(ContainerModelId, null));
data.Completed += (sender3, e3) =>
{
Rows = FormatData(data, headings);
shouldNotNeed.ItemsSource = Rows;
};
};
}
Assigning _vm.Rows to TheDataGrid.ItemsSource does not wire any change notification callback automatically. Try this:
in xaml:
<... x:Name=TheDataGrid ItemsSource={Binding Rows}>
In code:
this.DataContext = _vm;
As Codism points out your main problem is you need to use binding to take advantage of an INotifyPropertyChanged. However I would recommend this implementation pattern:-
public event PropertyChangedEventHandler PropertyChanged;
void NotifyPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name);
}
...
set
{
if (_rows != value)
{
_rows = value;
NotifyPropertyChanged("Rows");
}
}
Note that this approach minimises the impact on a an object instance whose properties are not being observed. In the original pattern you create instances of PropertyChangedEventArgs and calls to the event delegate going off regardless of whether anything is actually listening.
this.TheDataGrid.ItemsSource = _vm.Rows
When a collection is assigned as the ItemsSource of a DataGird , any changes made to the collection can be observed by the DataGrid if the source implements INotifyCollectionChanged.
From your code sample , I can't tell if the type SortableCollectionView implements INotifyCollectionChanged or inherits from ObservableCollection.
Implementing INotifyCollectionChanged would mean that you can't reset the backing field _rows for property Rows , you can clear items in the collection and add them as needed.
Hope this helps

Resources