Adding , removing items to and from a ListBox after binding - c#

In following button event I'm adding items to a list from another list.
private void btnAdd_Click(object sender, EventArgs e)
{
if (lstPermissions.SelectedItem != null)
if (!lstGivenPermissions.Items.Contains(lstPermissions.SelectedItem))
{
lstGivenPermissions.Items.Add(lstPermissions.SelectedItem);
}
}
When the Items were hard coded in lstPermissions and lstGivenPermissions's datasource is not set , it was fine. But After binding data to lstGivenPermissions, when I try to execute this method I'm getting this exception.
Items collection cannot be modified when the DataSource property is set.
I'm using this property to bind data to the lstGivenPermissions
public List<string> GivenPermission
{
get { return lstGivenPermissions.Items.Cast<string>().ToList(); }
set { lstGivenPermissions.DataSource = value; }
}
I can understand that the databinding has caused this exception. But my requirement is that I want to load all permissions to lstPermissions and selected user's permissions to lstGivenPermission from the database. Then I should be able to add and remove items to and from lstGivenPermissions. Could you let me know how to do this?

You shouldn't be using a property to bind to a list control... Properties should just save/load values, like so:
private List<string> _givenPermission;
public List<string> GivenPermission
{
get { return _givenPermission; }
set { _givenPermission = value;}
}
If you must bind, try doing it this way instead:
private List<string> _givenPermission;
public List<string> GivenPermission
{
get { return _givenPermission; }
set { _givenPermission = value; lstGivenPermissions.DataSource = value; }
}

Related

Event circularity

I find myself quite often in the following situation:
I have a user control which is bound to some data. Whenever the control is updated, the underlying data is updated. Whenever the underlying data is updated, the control is updated. So it's quite easy to get stuck in a never ending loop of updates (control updates data, data updates control, control updates data, etc.).
Usually I get around this by having a bool (e.g. updatedByUser) so I know whether a control has been updated programmatically or by the user, then I can decide whether or not to fire off the event to update the underlying data. This doesn't seem very neat.
Are there some best practices for dealing with such scenarios?
EDIT: I've added the following code example, but I think I have answered my own question...?
public partial class View : UserControl
{
private Model model = new Model();
public View()
{
InitializeComponent();
}
public event EventHandler<Model> DataUpdated;
public Model Model
{
get
{
return model;
}
set
{
if (value != null)
{
model = value;
UpdateTextBoxes();
}
}
}
private void UpdateTextBoxes()
{
if (InvokeRequired)
{
Invoke(new Action(() => UpdateTextBoxes()));
}
else
{
textBox1.Text = model.Text1;
textBox2.Text = model.Text2;
}
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
model.Text1 = ((TextBox)sender).Text;
OnModelUpdated();
}
private void textBox2_TextChanged(object sender, EventArgs e)
{
model.Text2 = ((TextBox)sender).Text;
OnModelUpdated();
}
private void OnModelUpdated()
{
DataUpdated?.Invoke(this, model);
}
}
public class Model
{
public string Text1 { get; set; }
public string Text2 { get; set; }
}
public class Presenter
{
private Model model;
private View view;
public Presenter(Model model, View view)
{
this.model = model;
this.view = view;
view.DataUpdated += View_DataUpdated;
}
public Model Model
{
get
{
return model;
}
set
{
model = value;
view.Model = model;
}
}
private void View_DataUpdated(object sender, Model e)
{
//This is fine.
model = e;
//This causes the circular dependency.
Model = e;
}
}
One option would be to stop the update in case the data didn't change since the last time. For example if the data were in form of a class, you could check if the data is the same instance as the last time the event was triggered and if that is the case, stop the propagation.
This is what many MVVM frameworks do to prevent raising PropertyChanged event in case the property didn't actually change:
private string _someProperty = "";
public string SomeProperty
{
get
{
return _someProperty;
}
set
{
if ( _someProperty != value )
{
_someProperty = value;
RaisePropertyChanged();
}
}
}
You can implement this concept similarly for Windows Forms.
What you're looking for is called Data Binding. It allows you to connect two or more properties, so that when one property changes others will be updated auto-magically.
In WinForms it's a little bit ugly, but works like a charm in cases such as yours. First you need a class which represents your data and implements INotifyPropertyChanged to notify the controls when data changes.
public class ViewModel : INotifyPropertyChanged
{
private string _textFieldValue;
public string TextFieldValue {
get
{
return _textFieldValue;
}
set
{
_textFieldValue = value;
NotifyChanged();
}
}
public void NotifyChanged()
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(null));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Than in your Form/Control you bind the value of ViewModel.TextFieldValue to textBox.Text. This means whenever value of TextFieldValue changes the Text property will be updated and whenever Text property changes TextFieldValue will be updated. In other words the values of those two properties will be the same. That solves the circular loops issue you're encountering.
public partial class Form1 : Form
{
public ViewModel ViewModel = new ViewModel();
public Form1()
{
InitializeComponent();
// Connect: textBox1.Text <-> viewModel.TextFieldValue
textBox1.DataBindings.Add("Text", ViewModel , "TextFieldValue");
}
}
If you need to modify the values from outside of the Form/Control, simply set values of the ViewModel
form.ViewModel.TextFieldValue = "new value";
The control will be updated automatically.
You should look into MVP - it is the preferred design pattern for Winforms UI.
http://www.codeproject.com/Articles/14660/WinForms-Model-View-Presenter
using that design pattern gives you a more readable code in addition to allowing you to avoid circular events.
in order to actually avoid circular events, your view should only export a property which once it is set it would make sure the txtChanged_Event would not be called.
something like this:
public string UserName
{
get
{
return txtUserName.Text;
}
set
{
txtUserName.TextChanged -= txtUserName_TextChanged;
txtUserName.Text = value;
txtUserName.TextChanged += txtUserName_TextChanged;
}
}
or you can use a MZetko's answer with a private property

Filtering a combobox with a textbox

As the title suggests, I want to filter the values in a combobox according to what is written in a textbox. The combobox takes values from a list. I have tried AutoCompleteMode and AutoCompleteSource but it doesn't let me add any values to the combobox when I use these. The combobox holds values of a list of the following class.
class Groep
{
//Fields
private string naamGroep;
//Properties
public string NaamGroep
{
get { return this.naamGroep; }
set { naamGroep = NaamGroep; }
}
//Constructor
public Groep(string naam)
{
this.naamGroep = naam;
}
This is the list:
List<Groep> Groepen = new List<Groep>();
I have two textboxes. One to add items to the list and the other to filter the combobox.
Do it using a foreach loop
private void Button1_Click(object sender, EventArgs e)
{
ComboBox1.Items.Clear();
foreach (Groep g in Groepen.Where(g => g.NaamGroep.Contains(TextBox1.Text)))
ComboBox1.Items.Add(g.NaamGroep);
}

Set the SelectionChanged event of a ComboBox while binding its SelectedItem and ItemsSource in XAML

I'm trying to set up a ComboBox with its options binded from a list of strings, its default selected value binded from a setting, and with an event handler for its selection changed.
I want to configure it all using XAML like so:
<ComboBox Name="RoutesComboBox"
ItemsSource="{Binding Routes}"
SelectedItem="{Binding DefaultRoute}"
SelectionChanged="RouteFilter_SelectionChanged" />
But when I do that on startup it throws the error:
An unhandled exception of type
'System.Reflection.TargetInvocationException' occurred in
PresentationFramework.dll
If I only do some of it in XAML, then either set the SelectionChanged event or the ItemsSource programatically in C# like below it works fine. But I have a lot of these ComboBoxes so I would rather do it straight in the XAML.
<ComboBox Name="RoutesComboBox"
ItemsSource="{Binding Routes}"
SelectedItem="{Binding DefaultRoute}" />
With this C#:
public IEnumerable<string> Routes
{
get { return LubricationDatabase.GetRoutes(); }
}
public string DefaultRoute
{
get { return MySettings.Default.DefaultRoute; }
set { } /* side question: without this, it throws a parse exception. Any idea why? */
}
public MainWindow()
{
this.DataContext = this;
InitializeComponent();
RoutesComboBox.SelectionChanged += RouteFilter_SelectionChanged;
}
I've also tried the solution found here:
private string _defaultRoute;
public string DefaultRoute
{
get { return MySettings.Default.DefaultRoute; }
set
{
if (_defaultRoute != value)
{
_defaultRoute = value;
// this fires before `SelectedValue` has been
// updated, and the handler function uses that,
// so I manually set it here.
RoutesComboBox.SelectedValue = value;
SelectionChangedHandler();
}
}
}
Which is okay, but is pretty bulky and probably more work than is worth it when I can just programatically assign the SelectionChanged event.
Again if possible I'd like to do it all using XAML because I have a lot of these ComboBoxes and initializing them all like this in the C# will look awful.
Any ideas?
Why are you binding with SelectedItem when you're not going to update the item when a user changes their selection? Not sure what your event handler is doing, but I have a working solution just the way you wanted it.
In short, you need to keep track of the DefaultRoute using a backing field. Also, you need to notify the UI when the selected item changes in your view model; which by the way is something you don't seem to be doing, MVVM. You should only be hooking into the selection changed event if you plan on updating the view in some way. All other changes should be handled in your view models DefaultRoute setter
XAML
<ComboBox Name="RoutesComboBox"
ItemsSource="{Binding Routes}"
SelectedItem="{Binding DefaultRoute}"
SelectionChanged="RouteFilter_SelectionChanged" />
Code
public partial class MainWindow : Window, INotifyPropertyChanged
{
public IEnumerable<string> Routes
{
get
{
return new string[] { "a", "b", "c", "d" };
}
}
public string DefaultRoute
{
get
{
return _defaultRoute;
}
set
{
_defaultRoute = value;
// Handle saving/storing setting here, when selection has changed
//MySettings.Default.DefaultRoute = value;
NotifyPropertyChanged();
}
}
public MainWindow()
{
this.DataContext = this;
InitializeComponent();
DefaultRoute = MySettings.Default.DefaultRoute;
}
private string _defaultRoute;
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void RouteFilter_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
}
}
public static class MySettings
{
public static class Default
{
public static string DefaultRoute = "a";
}
}

ObservableCollection filtering on Windows Phone 8.1 Universal

I am writing windows phone 8.1 universal application and main applicaiton control is Pivot with few pivot items. In the pivot items are ListViews containing TestItems. I want to filter items on one list by IsRead property. Is it possible to just filter main collection without keeping 2 collections? CollectionViewSource does not support filtering a sorting on universal apps, if I know. But keeping (and synchronizing on changes) two collections doesn't look like good idea.
EDIT:
I have used ObservableCollection because list of items may be updated on the background. Probably it was not clear from original question.
class TestItem : ModelBase
{
private bool isRead;
public bool IsRead
{
get { return isRead; }
set
{
isRead = value;
NotifyPropertyChanged();
}
}
private string name;
public string Name
{
get { return name; }
set
{
name = value;
NotifyPropertyChanged();
}
}
}
class MainViewModel : INotifyPropertyChanged
{
public MainViewModel()
{
Items = new ObservableCollection<TestItem>();
}
public ObservableCollection<TestItem> Items { get; private set; }
public ObservableCollection<TestItem> ItemsRead { get; private set; } // key point
private void RefreshItems()
{
// data manipulation - on both collections?
}
// ...
}
You can use Linq;
In your case:
using System.Linq;
class MainViewModel : INotifyPropertyChanged
{
public MainViewModel()
{
Items = new ObservableCollection<TestItem>();
}
public ObservableCollection<TestItem> Items { get; private set; }
//public ObservableCollection<TestItem> ItemsRead { get; private set; } // key point
public IEnumerable<TestItem> ItemsRead
{
get
{
IEnumerable<TestItem> itemsRead = from item in Items
where item.IsRead
select item;
return itemsRead;
}
}
private void RefreshItems()
{
// data manipulation - on both collections?
}
// ...
}
Please, check syntax, it can contain some mistakes.
You can manipulate with the first collection, the second collection will be automatically updated.
You can define a CollectionViewSource in your XAML:
<Grid.Resources>
<CollectionViewSource x:Name="MyCollectionViewSource"/>
</Grid.Resources>
And then set it's source like this:
//Global variable
MainViewModel vm;
//Constructor
public MyPage(){
//Other code
vm = new MainViewModel();
vm.Items.CollectionChanged += Items_CollectionChanged;
UpdateViewSource();
}
private void Items_CollectionChanged(object sender, CollectionChangedEventArgs e){
UpdateViewSource();
}
private void UpdateViewSource(){
MyCollectionViewSource.Source = vm.Items.Where(x => x.IsRead);
}
I haven't tested this code.
You need only one ObservableCollection containing the initial objects and another property (let's say ItemsFiltered) with a get method returning the results after filtering. In the constructor you can subscribe to the CollectionChanged event of the observable collection to raise the OnPropertyChanged event for the ItemsFiltered property. You raise the same event when the filter state is changed. This is a simple example:
public MainViewModel()
{
_initialItems.CollectionChanged += (sender, e) => OnPropertyChanged("Items");
}
private ObservableCollection<TestItem> _initialItems = new ObservableCollection<TestItem>();
public List<TestItem> Items
{
get
{
if (IsReadFilter)
{
return _initialItems.Where(i => i.IsRead).ToList();
}
return _initialItems;
}
}
private bool _isReadFilter;
public bool IsReadFilter
{
get { return _isReadFilter; }
set
{
if (_isReadFilter != value)
{
_isReadFilter = value;
OnPropertyChanged("IsReadFilter");
OnPropertyChanged("Items");
}
}
}
Basically, the idea is that every time IsReadFilter value is changed, the UI gets notified that the Items property is changed and calls its get method to get the new value and update. Items are also updated every time the observable collection is changed from other places.

WPF Binding and Custom ListView and ListViewItems

I need a WPF control that functions similar to the 'Resolve Conflicts' window in TFS, and other similar source control systems.
I have the following classes
public class Conflict:INotifyPropertyChanged
{
private string _name;
private List<Resolution> _resolutions;
private bool _focused;
private bool _hasResolutions;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name");
}
}
public List<Resolution> Resolutions
{
get { return _resolutions; }
set
{
_resolutions = value;
OnPropertyChanged("Resolutions");
}
}
public bool Focused
{
get { return _focused; }
set {
_focused = value;
OnPropertyChanged("Focused");
}
}
public bool HasResolutions
{
get { return _resolutions.Any(); }
set
{
_hasResolutions = value;
OnPropertyChanged("HasResolutions");
}
}
}
public class Resolution
{
public string Name { get; set; }
public void Resolve()
{
//Logic goes here
}
}
This almost identical to the functionality of the Team Foundation Server (TFS) 'Resolve Conflict' window shown below:
For each row in the image above, it is the same as my Conflcit object, and for each of the buttons, would be one of the Resolution objects on the Conflict object.
My plan was to bind my List to a ListView, and then write a custom template or whatever to hide/show the buttons below it based on if it was selected or not.
To try to simplify what I need to accomplish, I have a List and I want to bind it to a control, and it look as close to the image above as possible.
How would I accomplish this and XAML and the code behind?
Here is an example of how you can dynamically create a data template, and add buttons based on your Conflict objects:
public DataTemplate BuildDataTemplate(Conflict conflict)
{
DataTemplate template = new DataTemplate();
// Set a stackpanel to hold all the resolution buttons
FrameworkElementFactory factory = new FrameworkElementFactory(typeof(StackPanel));
template.VisualTree = factory;
// Iterate through the resolution
foreach (var resolution in conflict.Resolutions)
{
// Create a button
FrameworkElementFactory childFactory = new FrameworkElementFactory(typeof(Button));
// Bind it's content to the Name property of the resolution
childFactory.SetBinding(Button.ContentProperty, new Binding("Name"));
// Bind it's resolve method with the button's click event
childFactory.AddHandler(Button.ClickEvent, new Action(() => resolution.Resolve());
// Append button to stackpanel
factory.AppendChild(childFactory);
}
return template;
}
You can do this in many different ways and this is just one of them.
I haven't test it, but this should be enough to get you started :)
Good luck

Categories