I have a simple list of strings which I want to be displayed in a listbox depending on if a checkbox is checked when a button is pressed. I have this logic in my button listener:
private void fileSavePerms_Click(object sender, RoutedEventArgs e)
{
foreach (CheckBox checkbox in checkboxList)
{
if (checkbox.IsChecked == true && !permissionList.Contains(checkbox.Name))
{
permissionList.Add(checkbox.Name);
}
else if (checkbox.IsChecked == false && permissionList.Contains(checkbox.Name))
{
permissionList.Remove(checkbox.Name);
}
}
permListBox.ItemsSource = permissionList;
}
As far as I know, this is how you can do a very simple data-bind on button click. However the listbox updates for the first time as intended, but then will update with incorrect contents of the list I am trying to populate the box with. I can see no discernible pattern with the output.
Furthermore, after a while (a few button clicks), I will catch an exception saying "an ItemsControl is inconsistent with its items source".
Am I setting up my binding incorrectly or assigning the ItemsControl at the incorrect time?
Update:
The XAML for the list box:
<ListBox x:Name="permListBox" ItemsSource="{Binding permissionList}" HorizontalAlignment="Left" Height="36" Margin="28,512,0,0" VerticalAlignment="Top" Width="442"/>
First of all you can bind only properties to a control. A field cannot be bound. So permissionList must be a property of the DataContext object you set to your Window.DataContext property.
If this is correctly set then you can create a new List<string> every time and then assign it to the property bound to the control. You do not have to assign it to the ItemsSource property of the control
Let's say your window's data context is set to the window itself.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
}
public List<string> PermissionList
{
get { return (List<string>)GetValue(PermissionListProperty); }
set { SetValue(PermissionListProperty, value); }
}
public static readonly DependencyProperty PermissionListProperty =
DependencyProperty.Register(
"PermissionList",
typeof(List<string>),
typeof(MainWindow),
new PropertyMetadata(new List<string>())
);
private void fileSavePerms_Click(object sender, RoutedEventArgs e)
{
// You create a new instance of List<string>
var newPermissionList = new List<string>();
// your foreach statement that fills this newPermissionList
// ...
// At the end you simply replace the property value with this new list
PermissionList = newPermissionList;
}
}
In the XAML file you will have this:
<ListBox
ItemsSource="{Binding PermissionList}"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Margin="28,512,0,0"
Height="36"
Width="442"/>
Of course this solution can be improved.
You may use System.Collections.ObjectModel.ObservableCollection<string> type so that you no longer have to create a new instance of List<string> every time but you can clear the list and add the new items in your foreach statement.
You may use a ViewModel class (e.g. MainViewModel) that has this permission list and also implements the INotifyPropertyChanged interface and then you set an instance of this class to your WPF window's DataContext property.
Related
I have two textboxes with userinput, of which I need to transfer the data to my ViewModel. I tried looking around how to do this by binding it to a button (as the transfer is supposed to take place upon a buttonclick), but most advice to use bindings. However, to use bindings you have to declare properties in the ViewModel (afaik), but as these strings are used to create a new object, holding properties for them would be all but ideal because the two textboxes might expand to over 10 in the future. I've also tried messing around with CommandParameter but I only seem to be able to declare one.
So for clarification:
How do I transfer the contents of two (or more) textboxes to the corresponding ViewModel so I can create a new Object with them?
Edit:
In addition I'd also like to be able to reset the Text= field to be empty once the method handling the data has succesfully completed.
The View
<TextBox Name="UI1"/>
<TextBox Name="UI2"/>
<Button Source="*ImageSource*" Command="{Binding CallCreateObject}"/>
and the ModelView
private void OnCallCreateObject()
{
Object newObject = new Object(UI1, UI2, false)
}
This is a general example of what I'm trying to achieve
If you want to insert data from UI to ViewModel on Button Click than there is no reason to use binding. Binding is mainly used to sync data between UI and underlying models.
Still if you want only that then on button_click event you can do something like this.
private void button_Click(object sender, RoutedEventArgs e)
{
Model model = new Model();
model.Property1 = textBox1.Text;
model.Property2 = textBox2.Text;
textBox1.Text = string.Empty;
textBox2.Text = string.Empty;
}
That will solve your issue. But this approach is not recommended when you have a better thing that is called 'Binding'
If you want to bind your view with a viewmodel then try this:
Your view model:
public class Person : INotifyPropertyChanged
{
private string name;
// Declare the event
public event PropertyChangedEventHandler PropertyChanged;
public string PersonName
{
get { return name; }
set
{
name = value;
// Call OnPropertyChanged whenever the property is updated
OnPropertyChanged("PersonName");
}
}
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
Great, you have set up your view model. Now the view:
XML PersonView.xml:
<Grid Name="MyContainer">
<TextBox Text="{Binding PersonName}" />
<Button Name="SaveInfoButton" OnClick="SaveInfoButton_Click">Save info</Button>
</Grid>
Now that we have indicated with which property the textbox will be bind, lets indicate to the view the model that will use to update the property named PersonName. The idea is that when you click over the button, the property PersonName of our model Person gets updated with the value of the TextBox.
The xml class:
public partial class PersonView : UserControl
{
private readonly Person Model;
public PersonView()
{
//Components initialization, etc. etc...
this.Model = new Person();
this.DataContext = this.Model; // Here we are binding the model with our view.
}
private void SaveInfoButton_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(this.Model.PersonName); // this will print the value of your textbox.
}
}
Dont know if you noticed, but we didnt have the need of creating a new object when the user click the button. We just use our model and update the model properpies. If you add more textbox to your view, you'll have to added to our viewmodel as well as the given example.
Here is some post that can help you a little bit more(dont have enough time)
http://blog.scottlogic.com/2012/04/20/everything-you-wanted-to-know-about-databinding-in-wpf-silverlight-and-wp7-part-two.html
http://www.tutorialspoint.com/wpf/wpf_data_binding.htm
You could use bindings like this:
<TextBox Name="UI1" Text="{Binding Path=Ut1Value}"/>
<TextBox Name="UI2" Text="{Binding Path=Ut2Value}"/>
<Button Source="*ImageSource*" Command="{Binding CreateTheThingCommand}"/>
Then in your viewmodel you'll need to have the properties and command for those:
private string _ut1Value;
private string _ut2Value;
public string Ut1Value
{
get
{
return _ut1Value;
}
set
{
if (_ut1Value!= value)
{
_ut1Value= value;
OnPropertyChanged("Ut1Value");
}
}
}
public string Ut2Value
{
get
{
return _ut2Value;
}
set
{
if (_ut2Value!= value)
{
_ut2Value= value;
OnPropertyChanged("Ut2Value");
}
}
}
public ICommand CreateTheThingCommand
{
get { return new RelayCommand(CreateTheThing); }
}
private void CreateTheThing()
{
Object newObject = new Object(_ut1Value, _ut2Value, false);
// Do whatever with your new object
}
It sounds as if you need at least two ViewModel objects:
One to present the data from an existing object. This would be, essentially, what you have already.
A container ViewModel. This encapsulates the behaviours of the IEnumerable collection of objects, including the functionality required to Add a new object.
The container ViewModel would have the properties that you are struggling with, plus the CreateObject command, along with an IEnumerable (ObservableCollection) property to hold the existing ViewModel objects.
In your View, you would have one control to present the data in an existing ViewModel object, and a second control with a ListView (or similar) control to display the existing view controls and the set of TextBox controls, plus the button to create a new object (and add it to the list).
This would also allow you to add 'remove', 'sort', etc. functionality to the container ViewModel, without having to change the existing ViewModel.
A way to accomplish a scalable solution with minimal lines of code, would be to create hold a list of items you bind to in the view model.
This way you can use an ItemsControl in the UI to display a textbox for each item:
public class ViewModel
{
public List<Item> Items {get;} = new List<Item>
{
new Item { Value = "UI1" },
new Item { Value = "UI2" },
};
public class Item
{
public string Value {get;set;}
}
}
View:
<ItemsControl ItemsSource="{Binding Test}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Value}" Margin="5"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Content="Commit" Margin="5" Click="ButtonBase_OnClick"/>
You can then create the object either from a click event or command:
private void OnCallCreateObject()
{
Object newObject = new Object(Items[0], Items[1], false);
}
The downside is that the order of the items is not explicit, so either you need to assume that the indexed order is correct, or order them manually.
I have a button, When it's clicked it populates my Datagrid. The code is written within the .xaml.cs file, which I believe breaks the MVVM rule but it's just a temporary situation. I know it's not ideal for MVVM.
Calculate.xaml.cs
public void PopulateGrid(object sender, RoutedEventArgs e)
{
BindableCollection<Payments> PaymentCollection = new BindableCollection<Payments>
....
Datagrid.ItemsSource = PaymentCollection
....
}
My question is if there's a way to read the Datagrids ItemsSource From the ViewModel.
What I've Tried
LoansViewModel
public BindableCollection<Payments> paymentCollection {get; set;}
Calculate.xaml
<telerik:RadGridView ItemsSource="{Binding paymentCollection, Mode=TwoWay}" ... />
The collection paymentCollection Doesn't Update after calculate is clicked.
Just do this the correct MVVM way. Get rid of your PopulateGrid method in the .xaml.cs file and eliminate setting the Click property in your xaml. Instead bind the command property of the button to an ICommand property in your ViewModel the same way you are binding the ItemsSource of the RadGridView. You will need an implementation of ICommand to use and MVVM Lights RelayCommand is one option for that.
Here is the code for the ICommand:
private ICommand _populateGridCommand;
public ICommand PopulateGridCommand
{
get
{
if (_populateGridCommand == null)
{
_populateGridCommand = new RelayCommand(() => PopulateGrid());
}
return _populateGridCommand;
}
}
public void PopulateGrid()
{
PaymentCollection.Clear();
//load data and then add to the collection
}
UPDATE
To do this in code behind, you'll need to access the ViewModel and work on the collection from it. I don't like this but it should work.
public void PopulateGrid(object sender, RoutedEventArgs e)
{
var loansVM = DataGrid.DataContext as LoansViewModel;
loansVM.paymentsCollection.Clear();
var newData = //load data
foreach (var data in newData)
loansVM.paymentsCollection.Add(data);
}
Your xaml code looks like it should work provided the DataContext of your grid is set to your ViewModel instance where your paymentCollection property is declared.
Once your binding is set, it calls the get on the paymentCollection property. If your collection property object is not reassigned any further, and you add and remove elements from it, and it notifies on those changes via INotifyCollectionChanged, it will work. This is how ObservableCollection works and used most commonly for such scenarios.
However, if when you calculate, you re-assign your paymentCollection property with a new instance, your grid will not update, because you now have an entirely different collection. In that case you will need to notify the view that the paymentCollection property itself has changed. In which case you should implement it as a notification property:
private BindableCollection<Payments>_paymentCollection;
public BindableCollection<Payments> paymentCollection {
get { return _paymentCollection; }
set {
_paymentCollection = value;
OnPropertyChanged("paymentCollection");
}
}
protected void OnPropertyChanged(string name) {
PropertyChangedEventHandler handler = PropertyChanged;
if(handler != null) {
handler(this, new PropertyChangedEventArgs(name));
}
}
My problem is that the ComboBox is not displaying the value stored in its bound list.
Here is what I'm doing:
WPF:
<ComboBox ItemsSource="{Binding Devices}"
DropDownOpened="deviceSelector_DropDownOpened"/>
Note that my Window's DataContext is {Binding RelativeSource={RelativeSource Self}}.
C# code-behind:
public List<String> Devices { get; set; }
private void deviceSelector_DropDownOpened(object sender, EventArgs e)
{
// the actual population of the list is occuring in another method
// as a result of a database query. I've confirmed that this query is
// working properly and Devices is being populated.
var dev = new List<String>();
dev.Add("Device 1");
dev.Add("Device 2");
Devices = dev;
}
I have tried doing this with an ObservableCollection instead of a List, and I've also tried using a PropertyChangedEventHandler. Neither of these approaches have worked for me.
Any idea why my items aren't being displayed when I click the dropdown?
Since you're doing this in code behind anyway, why not set the ComboBox.ItemsSource directly.
Now, I am not going to say this is the way it should be done in WPF (I would prefer the view's data to be loaded in a ViewModel), but it will solve your issue.
The reason why this isn't working is because your property doesn't inform the binding system when it changes. I know you said you tried it with PropertyChangedEventHandler, but that won't work unless your View looks like this:
public class MyView : UserControl, INotifyPropertyChanged
{
private List<String> devices;
public event PropertyChangedEventHandler PropertyChanged;
public List<String> Devices
{
get { return devices; }
set
{
devices = value;
// add appropriate event raising pattern
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Devices"));
}
}
...
}
Likewise, using an ObservableCollection would only work like this:
private readonly ObservableCollection<string> devices = new ObservableCollection<string>();
public IEnumerable<string> Devices { get { return devices; } }
private void deviceSelector_DropDownOpened(object sender, EventArgs e)
{
devices.Clear();
devices.Add("Device 1");
devices.Add("Device 2");
}
Either method should populate the ComboBox, and in a quick test I just ran, it worked.
Edit to add DependencyProperty method
One last way you can do this is with a DependencyProperty (as your View is a DependencyObject:
public class MyView : UserControl
{
public static readonly DependencyProperty DevicesProperty = DependencyProperty.Register(
"Devices",
typeof(List<string>),
typeof(MainWindow),
new FrameworkPropertyMetadata(null));
public List<string> Devices
{
get { return (List<string>)GetValue(DevicesProperty); }
set { SetValue(DevicesProperty, value); }
}
...
}
The following change (suggested by Abe Heidebrecht) fixed the problem, but I don't know why. Anyone willing to lend an explanation?
WPF:
<ComboBox DropDownOpened="deviceSelector_DropDownOpened"
Name="deviceSelector"/>
C# code-behind:
private void deviceSelector_DropDownOpened(object sender, EventArgs e)
{
var dev = new List<String>();
dev.Add("Device 1");
dev.Add("Device 2");
deviceSelector.ItemsSource = dev;
}
Unless I'm missing something here:
Try firing OnPropertyChanged when devices gets updated for the devices property, this should fix this. I have occasionally also had to set the mode:
ItemsSource="{Binding Devices, Mode=TwoWay}"
On some controls.
Setting the itemssource on the control directly tells the control to use new items directly, without using the binding hooked up in the xaml. Updating the Devices property on the datacontext does not tell the combobox that the Devices property has changed so it won't update. The way to inform the combobox of the change is to fire OnPropertyChanged for the devices property when it gets changed.
I still see my self as a beginner developer when it comes to wpf. I would like to know 2 things
where I went wrong or
how can I troubleshoot to find the solution.
Situation: [DATA part] I Have:
DataModel object. DataFilter object which is basically collection of DataModels + added functions. and DataFiltersGroup, which is used in DataViewModel and has collection of DataFilters I have a DataViewModel object which is basically an observable collection of items. I want to display each DataFilter in an Itemscontrol.
[Current solution] I have build a specialcombo control which derives from combobox [basically a button +combobox]. The specialcombo works fine when deliberately bound. So I am fairly confident the problem is not with special combo. When I set ItemsControl.ItemsSource property to the collection of DataFilters and make a DataTemplate of SpecialCombo, the combobox does not show any result (Special combo will not show toggle button if there are no Items - only button will show). An alternative - approach (2) to binding below let me see the dropdown togglebutton, but dropdown is empty however I know it shouldn't be.
here is sumarized extracts of code
public class DataModel :INotifyPropertyChanged
{
//The Item!!
public int Index {//Normal get set property}
public string Name {//Normal get set property}
public int Parent {//Normal get set property}
public string FullName {//Normal get set property}
public string DisplayName {//Normal get set property}
public bool Static {//Normal get set property}
}
public class DataFilters : DataCollection
{
public ObservableCollection<DataModel> CombinedData;
public int FilterIndex{//Property... The index of the current item like Index for DataModel.}
public string ParentName {//property ButtonContent item}
public int SelectedItem {//Property}
}
//Used as part of DataVieModel. Also responsible of building each DataFilters item and some other functions
public class DataFilterGroup : INotifyPropertyChanged
{
public ObservableCollection<DataFilters> FullCollection;
}
The WPF object
<ItemsControl x:Name="PART_ListBox" HorizontalAlignment="Left" VerticalContentAlignment="Stretch" Margin="0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
Code behind for WPF on load
//DVM = DataVieModel with some other objects. Filters = DataFilterGroup
PART_ListBox.ItemsSource = DVM.Filters.FullCollection;
PART_ListBox.ItemTemplate = DataFilterTemplate;
//And DataTemplate (1) - shows no combobox
private static DataTemplate DataFilterTemplate
{
get
{
DataTemplate DFT = new DataTemplate();
DFT.DataType = typeof(DataFilters);
FrameworkElementFactory Stack = new FrameworkElementFactory(typeof(VirtualizingStackPanel));
Stack.SetValue(VirtualizingStackPanel.OrientationProperty, Orientation.Horizontal);
FrameworkElementFactory Item = new FrameworkElementFactory(typeof(SpecialCombo));
Item.SetValue(SpecialCombo.ButtonContentProperty, new Binding("ParentName"));
Item.SetValue(SpecialCombo.ItemsSourceProperty, "CombinedData");
Item.SetValue(SpecialCombo.DisplayMemberPathProperty, "DisplayName");
Item.SetValue(SpecialCombo.SelectedValuePathProperty, "Index");
Item.SetValue(SpecialCombo.SelectedValueProperty, "SelectedItem");
//Item.SetValue(SpecialCombo.ToggleVisibleProperty, new Binding("ComboVisibility"));
//Item.SetValue(SpecialCombo.SelectedValueProperty, new Binding("SelectedItem"));
Stack.AppendChild(Item);
DFT.VisualTree = Stack;
return DFT;
}
}
//And DataTemplate (2) - shows combobox with no items in dropdown
private static DataTemplate DataFilterTemplate
{
get
{
DataTemplate DFT = new DataTemplate();
DFT.DataType = typeof(DataFilters);
FrameworkElementFactory Stack = new FrameworkElementFactory(typeof(VirtualizingStackPanel));
Stack.SetValue(VirtualizingStackPanel.OrientationProperty, Orientation.Horizontal);
FrameworkElementFactory Item = new FrameworkElementFactory(typeof(SpecialCombo));
Item.SetValue(SpecialCombo.ButtonContentProperty, new Binding("ParentName"));
Item.SetValue(SpecialCombo.ItemsSourceProperty, new Binding("CombinedData"));
Item.SetValue(SpecialCombo.DisplayMemberPathProperty, "DisplayName");
Item.SetValue(SpecialCombo.SelectedValuePathProperty, "Index");
Item.SetValue(SpecialCombo.SelectedValueProperty, "SelectedItem");
//Item.SetValue(SpecialCombo.ToggleVisibleProperty, new Binding("ComboVisibility"));
//Item.SetValue(SpecialCombo.SelectedValueProperty, new Binding("SelectedItem"));
Stack.AppendChild(Item);
DFT.VisualTree = Stack;
return DFT;
}
}
Thanks to punker 76 in another post where I restructured the question (here -> Can one bind a combobox Itemssource from a datatemplate of a ItemsControl) slightly the following had to be done.
DataFilters should become a dependencyobject therefore
public class DataFilters : DataCollection
// should become
public class DataFilters : DependencyObject
Observalbe collection should also change. so
public ObservableCollection<DataModel> CombinedData;
// should become
public static readonly DependencyProperty CombinedData= DependencyProperty.Register("CombinedData", typeof(ObservableCollection<DataModel>), typeof(DataFilters), new FrameworkPropertyMetadata());
//and should become a property
public ObservableCollection<DataModel> CombinedData
{
get { return (ObservableCollection<DataModel>)GetValue(CombinedDataProperty); }
set { SetValue(CombinedDataProperty, value); }
}
Then in the DataTemplate file the following should change
Item.SetValue(SpecialCombo.ItemsSourceProperty, "CombinedData");
//Should change to
Item.SetBinding(SpecialCombo.ItemsSourceProperty, new Binding("CombinedData") );
I haven't gone through the entire code above, but I think all the above changes should all be needed in order to bind a combobox type item in a DataTemplate.
I have a listbox that is bound to an ObservableCollection of Names. Some of the items in the list will have a checkbox that is toggled on/off, indicating the item has been selected.
How do I create an ObservableCollection from the selected items of the first listbox following the Master-Details concept?
(I plan to use my MasterViewModel as the DataContext for my DetailsView which displays the selected items Collection.)
Thanks in advance!
Yeah, I've come across this before as well. ListBoxes and the like have a dependency property called 'SelectedItem', but the 'SelectedItems' (with an 's') property is not implemented as one.
The cleanest solution I've found is just to subclass the listbox, and create my own dependency property called 'SelectedItems'. No fun, but it's I think the best solution.
UPDATE
First our ViewModel:
class ViewModel : INotifyPropertyChanged
{
// Set up our collection to be read from the View
public ObservableCollection<String> Collection { get; private set; }
// This collection will maintain the selected items
public ObservableCollection<String> SelectedItems { get; private set; }
public ViewModel()
{
// Instantiate
this.Collection = new ObservableCollection<String>();
this.SelectedItems = new ObservableCollection<String>();
// Now let's monitor when this.SelectdItems changes
this.SelectedItems.CollectionChanged += SelectedItems_CollectionChanged;
// Fill our collection with some strings (1 to 10).
// (1) Generate the numbers 1 - 10
// (2) Convert each number to a string
// (3) Cast into a list so we can use foreach
// (4) Add each item to the collection.
Enumerable.Range(1, 10)
.Select(number => number.ToString())
.ToList()
.ForEach(this.Collection.Add);
// Remember! Never reset the ObservableCollection.
// That is, never say this.Collection = new... (or you'll break the binding).
// instead use this.Collection.Clear(), and then add the items you want to add
}
void SelectedItems_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
{
foreach (String str in this.SelectedItems)
System.Diagnostics.Debug.WriteLine("New item added {0}", str);
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Then our extended ListBoxEx:
class ListBoxEx : ListBox
{
// Use the 'new' keyword so that we 'hide' the base property.
// This means that binding will go to this version of SelectedItems
// rather than whatever the base class uses. To reach the base 'SelectedItems' property
// We just need to use base.SelectedItems instead of this.SelectedItems
// Note that we register as an observable collection.
new DependencyProperty SelectedItemsProperty =
DependencyProperty.Register("SelectedItems", typeof(ObservableCollection<String>), typeof(ListBoxEx));
// Accessor. Again, note the 'new'.
new public ObservableCollection<String> SelectedItems
{
get { return (ObservableCollection<String>) GetValue(SelectedItemsProperty); }
set { SetValue(SelectedItemsProperty, value); }
}
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
// Guard against ViewModel being null
if (this.SelectedItems != null)
{
// Clear the list
this.SelectedItems.Clear();
// (1) On selection changed. Get the new base.SelectedItems
// (2) Cast each item to a String ("Make a string collection")
// (3) Cast to list, and use foreach to add each item to
// this.SelectedItems (note this is different from the original base.SelectedItems)
base.SelectedItems.Cast<String>()
.ToList()
.ForEach(this.SelectedItems.Add);
}
}
}
And finally our View:
<Window.DataContext>
<lol:ViewModel />
</Window.DataContext>
<Grid>
<lol:ListBoxEx ItemsSource="{Binding Collection}" SelectedItems="{Binding SelectedItems}"
SelectionMode="Multiple"/>
</Grid>