I have data saved in the IsolatedStorage in my WP7 app, this data is a ObservableCollection
I then load the data into a observablecollection in the app that is databinded to a listview with a datatemplate
But when I do this (Or just add data to the databound list) in the constructor it fires a ListBox selectionchanged event, so before my app is fully loaded this happens.
I have an event for selectionchanged to show details about the clicked object and this crashes when this happens (Selectedindex is 0 for some reason so object 1 in the loaded list is selected automaticly when loaded)
public partial class MainPage : INotifyPropertyChanged
{
public ObservableCollection<Note> NotesCollection { get; set; }
public CollectionViewSource NotesViewSource;
private readonly IsolatedStorageSettings settings;
// Constructor
public MainPage()
{
InitializeComponent();
NotesCollection = new ObservableCollection<Note>();
settings = IsolatedStorageSettings.ApplicationSettings;
if (settings.Contains("Notes"))
{
NotesCollection = (ObservableCollection<Note>)settings["Notes"];
}
else
{
settings.Add("Notes", NotesCollection);
}
NotesViewSource.View.Refresh();
//var note = new Note("hej", "hej", DateTime.Now, DateTime.Now);
//NotesCollection.Add(note); this also fires the event
NotesViewSource = new CollectionViewSource { Source = NotesCollection };
DataContext = this;
ListBoxNotes.ItemsSource = NotesViewSource.View;
}
my Selectionchanged
private void ListBoxNotesSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (ListBoxNotes.SelectedIndex == -1)
return;
var note = ListBoxNotes.SelectedItem as Note;
if (!(note is Note)) return;
(Application.Current as App).Note = note;
ListBoxNotes.SelectedIndex = -1;
NavigationService.Navigate(new Uri("/Views/DetailsView.xaml", UriKind.Relative));
}
If you want to add items to the OC before any bindings may fire, then move the following line
InitializeComponent();
after the point where items are added. When this method is called, all the UI is created and bindings are set. You can right-click and go to definition to see it happening.
I would tie into the Loaded event.
Use a private and public. Notice the lowercase for the private.
private ObservableCollection<Note> notesCollection
Make SelectedIndex a public property and bind to it.
When you assign the private side set it to -1;
private int selectedIndex = -1;
By default the selected index is 0. And selected index changed is always going to fire when the app starts. You just need to set selectedIndex = -1 before the event is called.
With SelectedIndex as a public property I would do the logic in the set and not even have a changed event.
Related
I'm trying to bind my Winforms UI to my ViewModel. I was able to successfully update my ViewModel on UI changes and vice versa. However, I can't seem to understand what is the use of "PropertyName" used in PropertyChangedEventHandler since whatever I put there, it will always work. I don't know if I've already mixed things up since I've read a lot of articles about architectural patterns (MVP,MVC,MVVM,and MVP-VM (which is the one I was trying to do now) ).
Here is the part of the concerned code:
ViewModel
public class AdditionViewModel:INotifyPropertyChanged
{
private string augend;
public string Augend
{
get { return augend; }
set {
if(augend != value)
{
augend = value;
OnPropertyChanged(new PropertyChangedEventArgs("ugend"));
}
}
}
private string addend;
public string Addend
{
get { return addend; }
set {
if (addend != value)
{
addend = value;
OnPropertyChanged(new PropertyChangedEventArgs("ddend"));
}
}
}
private string answer;
public string Answer
{
get { return answer; }
set {
if(answer != value)
{
answer = value;
OnPropertyChanged(new PropertyChangedEventArgs("nswer"));
}
}
}
public AdditionClass additionClass;
public AdditionViewModel(AdditionClass _additionClass)
{
additionClass = _additionClass;
}
public void Add()
{
//Some verifications first before inserting the value to the model class
this.Augend = "1";//Testing for from code to UI binding
additionClass.Augend = Double.Parse(Augend);
additionClass.Addend = Double.Parse(Addend);
//Somewhere here should implement the compute but since addition is a very simple task, no methods were called;
Answer = additionClass.Answer.ToString();
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(PropertyChangedEventArgs e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
MessageBox.Show(e.PropertyName);
handler(this, new PropertyChangedEventArgs(e.PropertyName));
}
}
}
Form:
private void Form1_Load(object sender, EventArgs e)
{
additionPresenter = new Presenter.AdditionPresenter(new ViewModel.AdditionViewModel(new Model.AdditionClass()));
additionViewModelBindingSource.DataSource = additionPresenter.additionViewModel;
}
private void button1_Click(object sender, EventArgs e)
{
additionPresenter.AddButtonClick();
}
Presenter:
public AdditionPresenter(AdditionViewModel _additionViewModel)
{
additionViewModel = _additionViewModel;
}
public void AddButtonClick()
{
additionViewModel.Add();
}
One of the auto-generated code from Designer (Binding on UI):
//
// textBox1
//
this.textBox1.DataBindings.Add(new System.Windows.Forms.Binding("Text", this.additionViewModelBindingSource, "Addend", true));
this.textBox1.Location = new System.Drawing.Point(24, 41);
this.textBox1.Name = "textBox1";
this.textBox1.Size = new System.Drawing.Size(100, 20);
this.textBox1.TabIndex = 0;
As can be seen on the ViewModel, I've omitted all the "A"s at the start of each PropertyName in the setters but the application is still working.
Sorry for the long code pastes. I can't seem to find a better explanation than just to show you the implementation
INotifyPropertyChanged is not necessary for data binding, but it enables two-way data binding.
In fact as mentioned in documentations: The INotifyPropertyChanged interface is used to notify clients, typically binding clients, that a property value has changed.
In simple (one-way) data binding, when you change the bound property of control, value push into the bound property of your object and it doesn't need INotifyPropertyChanges.
But without INotifyPropertyChanged, if you change the value of bound property of your object using code, new value doesn't push into your control's bound property.
Having wrong property names in PropertyChanged event why do I still have two-way data-binding?
In fact it's because of using BindingSource as source of data-boinding, as mentioned by Fabio in comments.
When using BindingSource as data source of your data-bindings, it's enough for your objects to implement INotifyPropertyChanged and raise PropertyChaned event (even with empty or wrong property name) and then the BindingSource (actually its inner BindingList<T>) subscribes for PropertyChaned event and when received the event it checkes if you didn't passed a correct property name or if you passed empty property name it the will call ResetBindings() that consequencly causes a control bound to the BindingSource to reread all the items in the list and refresh their displayed values.
Correct names in PropertyChanged causes the normal behavior of two-way data-binding and also causes raising ListChanged event with correct property in e.PropertyDescriptor.
C#:
public partial class MainWindow : Window
{
private readonly ViewModel vm;
public MainWindow()
{
InitializeComponent();
vm = new ViewModel();
DataContext = vm;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
vm.Models.RemoveAt(0);
}
}
public class ViewModel
{
public ObservableCollection<Model> Models { get; set; }
public ListCollectionView View { get; set; }
public ViewModel()
{
Models = new ObservableCollection<Model>()
{
new Model() { Name = "Gordon Freeman" },
new Model() { Name = "Isaac Kleiner" },
new Model() { Name = "Eli Vance" },
new Model() { Name = "Alyx Vance" },
};
Models.CollectionChanged += (s, e) => View.Refresh();
View = new ListCollectionView(Models);
}
}
public class Model
{
public string Name { get; set; }
public override string ToString()
{
return Name;
}
}
XAML:
<StackPanel>
<ListBox ItemsSource="{Binding Path=View}" />
<Button Click="Button_Click">Click</Button>
</StackPanel>
The ObservableCollection contains 4 elements, and the ListBox is displaying all 4, as expected. When the button is clicked, the 1st element of the ObservableCollection is removed. The ListBox, however, is now displaying only the 2nd and 3rd. It would appear the 1st and 4th have been removed.
If the line Models.CollectionChanged += (s, e) => View.Refresh(); is moved after View = new ListCollectionView(Models); (or commented out entirely) things work as expected.
Why?
P.S. This is a simple piece of a larger puzzle. In this small example, I realize I don't need to call View.Refresh(); on CollectionChanged for the ListBox to update itself.
My guess would be that the refresh interferes with the automatic update by the view. Presumably the view subscribes to CollectionChanged as well in the constructor, so if you also subscribe to the event before the view does and call refresh you get an unwanted update in between the collection change and the view's own update.
e.g.
Item 0 is removed -> Notify event listeners
=> Your handler: Refresh() -> Rebuild view => Item gets removed.
=> View handler: Event args say: Item X was removed -> Remove item X
This still does not explain why the first and the last items get removed but it seems reasonable to me.
If the subscription is after the view instantiation:
Item 0 is removed -> Notify event listeners
=> View handler: Event args say: Item X was removed -> Remove item X
=> Your handler: Refresh() -> Rebuild view => Nothing changed.
Seems to be a problem with ListView refresh. If you bind a labe/TextBlock to ListView.Items.Count, you'll see that the list still has 3 items (after first delete).
Even though you're wrapping your Model class in an ObservableCollection, you still need to implement INotifyPropertyChanged for it. I've banged my head against this problem enough times that now I remember to start troubleshooting at the lowest building block, i.e. Model.
public class Model : INotifyPropertyChanged
{
#region INotify Implementation
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#endregion
public string _Name;
public string Name {
get { return _Name; }
set { _Name = value; NotifyPropertyChanged("Name"); }
}
public override string ToString()
{
return Name;
}
}
Also, in your ViewModel, when INotifyPropertyChanged is implemented correctly you don't need the below lines. INotifyPropertyChanged will update your UI for you.
Models.CollectionChanged += (s, e) => View.Refresh();
View = new ListCollectionView(Models);
Try not to use the new keyword to define your new instance of listCollectionView. Use the following instead.
var sortedCities = CollectionViewSource.GetDefaultView(Models);
Is there an easy method to prompt the user to confirm a combo box selection change and not process the change if the user selected no?
We have a combo box where changing the selection will cause loss of data. Basically the user selects a type, then they are able to enter attributes of that type. If they change the type we clear all of the attributes as they may no longer apply. The problem is that to under the selection you raise the SelectionChanged event again.
Here is a snippet:
if (e.RemovedItems.Count > 0)
{
result = MessageBox.Show("Do you wish to continue?",
"Warning", MessageBoxButton.YesNo, MessageBoxImage.Warning);
if (result == MessageBoxResult.No)
{
if (e.RemovedItems.Count > 0)
((ComboBox)sender).SelectedItem = e.RemovedItems[0];
else
((ComboBox)sender).SelectedItem = null;
}
}
I have two solutions, neither of which I like.
After the user selects 'No', remove the SelectionChanged event handler, change the selected item and then register the SelectionChanged event handler again. This means you have to hold onto a reference of the event handler in the class so that you can add and remove it.
Create a ProcessSelectionChanged boolean as part of the class. Always check it at the start of the event handler. Set it to false before we change the selection back and then reset it to true afterwards. This will work, but I don't like using flags to basically nullify an event handler.
Anyone have an alternative solution or an improvement on the ones I mention?
I found this good implementation.
private bool handleSelection=true;
private void ComboBox_SelectionChanged(object sender,
SelectionChangedEventArgs e)
{
if (handleSelection)
{
MessageBoxResult result = MessageBox.Show
("Continue change?", MessageBoxButton.YesNo);
if (result == MessageBoxResult.No)
{
ComboBox combo = (ComboBox)sender;
handleSelection = false;
combo.SelectedItem = e.RemovedItems[0];
return;
}
}
handleSelection = true;
}
source: http://www.amazedsaint.com/2008/06/wpf-combo-box-cancelling-selection.html
Maybe create a class deriving from ComboBox, and override the OnSelectedItemChanged (Or OnSelectionChangeCommitted.)
Validating within the SelectionChanged event handler allows you to cancel your logic if the selection is invalid, but I don't know of an easy way to cancel the event or item selection.
My solution was to sub-class the WPF combo-box and add an internal handler for the SelectionChanged event. Whenever the event fires, my private internal handler raises a custom SelectionChanging event instead.
If the Cancel property is set on the corresponding SelectionChangingEventArgs, the event isn't raised and the SelectedIndex is reverted to its previous value. Otherwise a new SelectionChanged is raised that shadows the base event. Hopefully this helps!
EventArgs and handler delegate for SelectionChanging event:
public class SelectionChangingEventArgs : RoutedEventArgs
{
public bool Cancel { get; set; }
}
public delegate void
SelectionChangingEventHandler(Object sender, SelectionChangingEventArgs e);
ChangingComboBox class implementation:
public class ChangingComboBox : ComboBox
{
private int _index;
private int _lastIndex;
private bool _suppress;
public event SelectionChangingEventHandler SelectionChanging;
public new event SelectionChangedEventHandler SelectionChanged;
public ChangingComboBox()
{
_index = -1;
_lastIndex = 0;
_suppress = false;
base.SelectionChanged += InternalSelectionChanged;
}
private void InternalSelectionChanged(Object s, SelectionChangedEventArgs e)
{
var args = new SelectionChangingEventArgs();
OnSelectionChanging(args);
if(args.Cancel)
{
return;
}
OnSelectionChanged(e);
}
public new void OnSelectionChanged(SelectionChangedEventArgs e)
{
if (_suppress) return;
// The selection has changed, so _index must be updated
_index = SelectedIndex;
if (SelectionChanged != null)
{
SelectionChanged(this, e);
}
}
public void OnSelectionChanging(SelectionChangingEventArgs e)
{
if (_suppress) return;
// Recall the last SelectedIndex before raising SelectionChanging
_lastIndex = (_index >= 0) ? _index : SelectedIndex;
if(SelectionChanging == null) return;
// Invoke user event handler and revert to last
// selected index if user cancels the change
SelectionChanging(this, e);
if (e.Cancel)
{
_suppress = true;
SelectedIndex = _lastIndex;
_suppress = false;
}
}
}
In WPF dynamically set the object with
if (sender.IsMouseCaptured)
{
//perform operation
}
I do not believe using the dispatcher to post (or delay) a property update is a good solution, it is more of a workaround that is not really needed. The following solution i fully mvvm and it does not require a dispatcher.
First Bind the SelectedItem with an Explicit binding Mode. //this enables us to decide whether to Commit using the UpdateSource() method the changes to the VM or to Revert using the UpdateTarget() method in the UI.
Next, add a method to the VM that confirms if the change is allowed (This method can contain a service that prompts for user confirmation and returns a bool).
In the view code behind hook to the SelectionChanged event and update the Source (i.e., the VM) or the Target (i.e. the V) in accordance to whether the VM.ConfirmChange(...) method returned value as follows:
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if(e.AddedItems.Count != 0)
{
var selectedItem = e.AddedItems[0];
if (e.AddedItems[0] != _ViewModel.SelectedFormatType)
{
var comboBoxSelectedItemBinder = _TypesComboBox.GetBindingExpression(Selector.SelectedItemProperty); //_TypesComboBox is the name of the ComboBox control
if (_ViewModel.ConfirmChange(selectedItem))
{
// Update the VM.SelectedItem property if the user confirms the change.
comboBoxSelectedItemBinder.UpdateSource();
}
else
{
//otherwise update the view in accordance to the VM.SelectedItem property
comboBoxSelectedItemBinder.UpdateTarget();
}
}
}
}
This is an old question, but after struggling with the issue time and again I came up with this solution:
ComboBoxHelper.cs:
public class ComboBoxHelper
{
private readonly ComboBox _control;
public ComboBoxHelper(ComboBox control)
{
_control = control;
_control.PreviewMouseLeftButtonDown += _control_PreviewMouseLeftButtonDown; ;
_control.PreviewMouseLeftButtonUp += _control_PreviewMouseLeftButtonUp; ;
}
public Func<bool> IsEditingAllowed { get; set; }
public Func<object, bool> IsValidSelection { get; set; }
public Action<object> OnItemSelected { get; set; }
public bool CloseDropDownOnInvalidSelection { get; set; } = true;
private bool _handledMouseDown = false;
private void _control_PreviewMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
var isEditingAllowed = IsEditingAllowed?.Invoke() ?? true;
if (!isEditingAllowed)
{
e.Handled = true;
return;
}
_handledMouseDown = true;
}
private void _control_PreviewMouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (!_handledMouseDown) return;
_handledMouseDown = false;
var fe = (FrameworkElement)e.OriginalSource;
if (fe.DataContext != _control.DataContext)
{
//ASSUMPTION: Click was on an item and not the ComboBox itself (to open it)
var item = fe.DataContext;
var isValidSelection = IsValidSelection?.Invoke(item) ?? true;
if (isValidSelection)
{
OnItemSelected?.Invoke(item);
_control.IsDropDownOpen = false;
}
else if(CloseDropDownOnInvalidSelection)
{
_control.IsDropDownOpen = false;
}
e.Handled = true;
}
}
}
It can be used in a custom UserControl like this:
public class MyControl : UserControl
{
public MyControl()
{
InitializeComponent();
var helper = new ComboBoxHelper(MyComboBox); //MyComboBox is x:Name of the ComboBox in Xaml
helper.IsEditingAllowed = () => return Keyboard.Modifiers != Modifiers.Shift; //example
helper.IsValidSelection = (item) => return item.ToString() != "Invalid example.";
helper.OnItemSelected = (item) =>
{
System.Console.WriteLine(item);
};
}
}
This is independent of the SelectionChanged event, there are no side effects of the event firing more often than required. So others can safely listen to the event, e.g. to update their UI. Also avoided: "recursive" calls caused by resetting the selection from within the event handler to a valid item.
The assumptions made above regarding DataContext may not be a perfect fit for all scenarios, but can be easily adapted. A possible alternative would be to check, if the ComboBox is a visual parent of e.OriginalSource, which it isn't when an item is selected.
I like to populate a Listbox with a List<T> as the ItemsControl.ItemsSource. It seems like as soon as I set the ItemsSource, my event handler for SelectionChanged fires, and my first item in the list is selected. I'd like to start the list up with nothing selected by default, and not have to try to handle the event until the user actually clicks on a member of the list.
This is in a PopUp window, so each time the list appears, it should be set to 'nothing selected', i.e. SelectedIndex == -1.
I suppose I could remove the event handler in the code, populate the list, set it to -1, and then add the event handler again, but seems like a kind of messy way to do it.
Psychic debugging, do you have ListBox.IsSynchronizedWithCurrentItem set to True in the XAML?
Given:
<ListBox x:Name="TestListBox"
IsSynchronizedWithCurrentItem="True"/>
And:
public MainWindow()
{
InitializeComponent();
this.TestListBox.SelectionChanged += TestListBox_SelectionChanged;
this.TestListBox.ItemsSource = Enumerable.Range(10, 10).ToList();
}
void TestListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
throw new NotImplementedException();
}
I receieve a NotImplementException, meaning IsSynchronizedWithCurrentItem="True" is a likely culprit.
We can test hypothesis this by removing this attribute or setting it to false, which will not throw an exception until the user clicks on an item.
The other way is to bind the list to a public property, with a separate property for the current item.
<ListBox ItemsSource="{Binding Path=Images}" SelectedItem="{Binding CurrentItem}" />
private IndexedImage _currentItem;
public IndexedImage CurrentItem
{
get { return _currentItem; }
set
{
if (_currentItem == value) return;
_currentItem = value;
RaisePropertyChanged("CurrentItem");
}
}
private ObservableCollection<IndexedImage> _images;
public ObservableCollection<IndexedImage> Images
{
get { return _images ?? (_images = new ObservableCollection<IndexedImage>()); }
set
{
if (_images == value) return;
_images = value;
RaisePropertyChanged("Images");
}
}
CurrentItem is then totally under your control. When you start it's null and it gets set when they click an item or you set it manually.
For a ListBox (With Selection mode set to One), I wish to track whether there's a selected item or none selected. To do so, I subscribed a method to SelectedIndexChanged and checked if the SelectedIndex is -1 or not. However, I noticed that the event doesn't fire after calling Items.Clear(), even though SelectedIndex changes to -1 (if it wasn't already -1).
Why doesn't it fire?
I know I can work around this by assigning -1 to SelectedIndex before clearing the list. But is there a better way?
Here's a simple code to replicate this:
using System;
using System.Windows.Forms;
namespace ns
{
class Program
{
static ListBox lst = new ListBox();
public static void Main()
{
lst.SelectedIndexChanged += new EventHandler(lst_SelectedIndexChanged);
lst.Items.Add(1);
Console.WriteLine("Setting selected index to 0...");
lst.SelectedIndex = 0; //event fire here
Console.WriteLine("(Selected Index == {0})", lst.SelectedIndex);
Console.WriteLine("Clearing all items...");
lst.Items.Clear(); //event *should* fire here?!
//proof that the selected index has changed
Console.WriteLine("(Selected Index == {0})", lst.SelectedIndex);
}
static void lst_SelectedIndexChanged(object sender, EventArgs e)
{
Console.WriteLine("[!] Selected Index Changed:{0}", lst.SelectedIndex);
}
}
}
Edit:
I am considering making a custom list by making a class that inherits from ListBox, or by making a user control. However I'm not sure how to approach this.
Any ideas on hiding/overriding the clear method using either inheritance/userControl?
Would it require hiding/overriding other methods as well or is there a way to avoid this?
Looking at the code in Reflector, the Clear() method on Items just resets the .Net object's internal object list (and does not, as you noticed, fire OnSelectedIndexChanged).
The SelectedIndex property returns -1 because the logic in the property's getter dictates that -1 should be returned if there are no items in the internal list.
Clear() only clears the internal collection of the control. Clear() won't fire the SelectedIndexChanged event because that event will only be raised by changing the CurrentlySelectedIndex. Try using lst.ClearSelected() instead. Calling this method is equivalent to setting the SelectedIndex property to negative one (-1). You can use this method to quickly unselect all items in the list. Alternatively you can try calling Items.Clear() and follow it with a call to ListBox.RefreshItems
probably a hackish solution but this is what i thought of:
class myListBox
{
public ListBox myList;
public myListBox()
{
myList = new ListBox();
}
public void listClear()
{
if (myList.Items.Count > 0)
{
myList.SelectedIndex = 0;
}
myList.Items.Clear();
}
}
than you can call this like this in your main form:
myListBox example = new myListBox();
example.myList.Items.Add("Example");
example.myList.SelectedIndexChanged += new EventHandler(lst_SelectedIndexChanged);
this.Controls.Add(example.myList);
example.listClear();
maybe that could solve your problem.
This is my way, it's compatible with existed code.
public class DetailsListView : ListView
{
public new class ListViewItemCollection : ListView.ListViewItemCollection
{
private DetailsListView m_owner;
public ListViewItemCollection(DetailsListView owner)
: base(owner)
{
m_owner = owner;
}
public override void Clear()
{
base.Clear();
m_owner.FireChanged();
}
}
private void FireChanged()
{
base.OnSelectedIndexChanged(EventArgs.Empty);
}
private ListViewItemCollection m_Items;
public DetailsListView()
{
m_Items = new ListViewItemCollection(this);
View = View.Details;
GridLines = true;
HideSelection = false;
FullRowSelect = true;
}
public new ListViewItemCollection Items
{
get
{
return m_Items;
}
}
}