MVVM Listbox Update Content Maintain Selected Item Silverlight - silverlight

I've been reading a lot about MVVM (using Laurent Bugnion's library in specific) and I'm constantly struggling to determine how to do things in MVVM that were otherwise easy with code behind.
Here's just one example where I suspect I'm doing things the hard way. If anyone has the time to read all this, perhaps they can comment on the sanity of my approach. :)
I have a list box bound to a ViewModel like so:
<ListBox x:Name="lstFruitBasketLeft" ItemsSource="{Binding FruitBasket}"
SelectedItem="{Binding SelectedFruit, Mode=TwoWay}" Width="150">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center"
HorizontalAlignment="Left" Margin="2">
<TextBlock Text="{Binding Name}" />
<TextBlock Text=":" />
<TextBlock Text="{Binding Quantity}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
The ItemSource is an ObservableCollection of Fruit objects:
public class Fruit
{
public string Name { get; set; }
public int Quantity { get; set; }
public Fruit() { }
public Fruit(string name, int quantity)
{
this.Name = name;
this.Quantity = quantity;
}
}
It is defined in the ViewModel as:
// Property FruitBasket
public const string FruitBasketPropertyName = "FruitBasket";
private ObservableCollection<Fruit> _fruitBasket = null;
public ObservableCollection<Fruit> FruitBasket
{
get { return _fruitBasket; }
set
{
if (_fruitBasket == value)
return;
_fruitBasket = value;
// Update bindings, no broadcast
RaisePropertyChanged(FruitBasketPropertyName);
}
}
The bound SelectedItem property is as such:
//Property SelectedFruit
public const string SelectedFruitPropertyName = "SelectedFruit";
private Fruit _selectedFruit = null;
public Fruit SelectedFruit
{
get { return _selectedFruit; }
set
{
if (_selectedFruit == value)
return;
var oldValue = _selectedFruit;
_selectedFruit = value;
// Update bindings, no broadcast
RaisePropertyChanged(SelectedFruitPropertyName);
}
}
Then, the list is populated on the construction of the ViewModel.
Now, I add a RelayCommand to a button on the presentation page that executes a method which increments the quantity of the selected item. Note that I am not using the parameter yet, but "Bob" is a placeholder for some changes for later.
<Button x:Name="butMore" Content="More!" HorizontalAlignment="Right" Height="25" Width="75" Margin="4">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cmd:EventToCommand
Command="{Binding addMoreCommand}"
CommandParameter="Bob" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
Here's the code for the command:
// Property addMoreCommand
public RelayCommand addMoreCommand
{
get;
private set;
}
...
//Init relays (this is in the constructor)
addMoreCommand = new RelayCommand(AddFruit, CanExecute);
...
public void AddFruit()
{
//Increment the fruit
SelectedFruit.Quantity++;
//Save the previous selected item
Fruit oldSelectedItem = SelectedFruit;
//We have to have a new list in order to get the list box to refresh
FruitBasket = new ObservableCollection<Fruit>(FruitBasket);
//Reselect
SelectedFruit = oldSelectedItem;
}
public bool CanExecute()
{
return true; //for now
}
Now this does work, but I have some problems with it:
First, I feel like there are a lot of conditions that have to come together for this to work and I wonder if I'll get so lucky trying to move some Telerik Drag and Drop code into MVVM.
Second, it seems like a pretty poor performance approach to recreate the list like that.
Lastly, it seems like this would be easier in code behind (though I'm not 100% certain I still won't have to rebuild that list).
Does anyone have any thoughts on my approach or perhaps even... suggestions to make things easier? Am I just missing something obvious here?
Thanks
-Driodilate :]

maulkye,
There is something going wrong if you have to refresh your ObservableCollection. Usually, you should not need it because the ObservableCollection will notify about item changes.
Never do this:
FruitBasket = new ObservableCollection<Fruit>(FruitBasket);
Your public ObservableCollection<Fruit> FruitBasket should have no public setter, it should be read only. Just Add or Remove Items to/from the list.
If you want to handle multiple selections, you will probably need an extended CollectionView which can handle this, get more hints here.
I hope this helps a little bit, even if I probably didn't answer all questions : )
EDIT:
Ok, I guess i got some things wrong. Now i guess i fully understand what you're trying to accomplish. You are not getting notified when your property is changed, right? Well, for this reason, we've adapted "BindableLinq" in one of our projects, which you can compile in Silverlight without problems. (there are similar solutions available, called Continuous Linq or Obtics, make your choice).
Using BindableLinq, you can transform your ObservableCollection to a BindableCollection using one single extension method. The BindableCollection will then reflect all changes properly. Give it a try.
EDIT2:
To implement a proper ViewModel, Please consider the following Changes.
1) Fruit is your Model. Since it doesn't implement INotifyPropertyChanged, it won't propagate any changes. Create a FruitViewModel, embedding your Fruit Model and invoke RaisePropertyChanged for each property setter.
2) Change your FruitBasket to be an ObservableCollection of FruitViewModel. Slowly it starts to make sense :)
3) SelectedFruit has to be a FruitViewModel as well. Now it makes even more sense.
4) Now it already works for me, even without BindableLinq. Did you have any success?
HTH
best regards,
thomas

Related

CheckBox as datatemplate in a ListBox reset IsChecked value when scrolling

I want to start using MVVM in my project so I have started to investigate it.
While I was playing a bit with WPF I've encountered a bug that I couldn't find a solution to him by myself and while exploring internet.
I have something like that(I can't paste my full code because its not in the same network):
MainView.Xaml
<ListBox ItemsSource="{Binding Persons}">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Name}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Checked">
<my:AddToInvitation />
</i:EventTrigger>
<i:EventTrigger EventName="Unchecked">
<my:RemoveFromInvitation />
</i:EventTrigger>
</i:Interaction.Triggers>
</CheckBox>
</DataTemplate>
</ListBox.ItemTemplate>
MainViewModel.cs
public ObservableCollection<PersonViewModel> Persons { get; set; }
public MainViewModel()
{
this.Persons = new ObservableCollection<PersonViewModel>();
for(int i=0;i<1000;i++)
{
PersonViewModel personVM = new PersonViewModel (string.Format("Person - {0}",i));
this.Persons.add(personVM);
}
}
PersonViewModel.cs
private Person PersonObject { get; set; }
public string Name
{
get
{
return this.PersonObject.Name;
}
}
public PersonViewModel(string personName)
{
this.PersonObject = new Person(personName);
}
Person.cs
public string Name { get; set; }
public Person(string name)
{
this.Name = name;
}
Now if you try to paste it and run it, it will look just fine.
The problem is when you try the following instructions:
1) Check the first 10 persons in the ListBox.
2) Scroll down the ListBox to the bottom of it.
3) Leave the mouse when the list box is scrolled down.
4) Scroll back up to the top of the ListBox.
5) Poof! you'r checking disappeared.
Now the solution i have found to this is to add IsChecked property(Though I don't really need it) to the PersonViewModel and bind it to the CheckBox IsChecked DependencyProperty, but then I have added a functionality that lets the user to press a button and it will iterate over all the persons in the ListBox and change it IsChecked property to true(Button -> Select all).
Following to the disappear Checks bug I have crossed another bug which I believe somehow is connected to the disappearing Checks - the actions that I have put to trigger when Check and Uncheck occurs would trigger only for some of the CheckBoxes when you select all.
I tried to count how many times the actions would happen when I used the select all function and I found a connection between the height of the ListBox(Current Visible CheckBoxes) and the amount of the triggers that fired, furthermore I scrolled to the middle of the ListBox and used the SelectAll functionality and the triggers didn't fire until the loop encountered the first visible ChekBox that I can see in my ListBox.
Its a bit hard to understand this bug if you don't try it, so please comment here only if you tried this.
Thanks in advance!
The simple answer is: You are going against the current.
The binding is all about changing value in your ViewModel and allowing you to write your code against simple view model classes so that your presentation logic is free of business logic. In your example the decision to execute AddToInvitation RemoveFromInvitation is in your view and it should not be there.
You will be good with bool IsInvited{get;set;} property that is easily bound to checkbox (no dependency property required). And this will allow user changes to be persisted in your view model. If you need some other more complicated logic you should attach to PropertyChagned event form INotifyPropertyChanged interface that your ViewModel must implement. Then you can change property in your simple class at will and ui will update accordingly.

Bind ListBox to another ListBox

I have 2 listBoxes, if you click an item in the top one, then the bottom one filters to a few results.
I am trying to learn WPF and MVVM and am wondering if this is the correct way to do this. Is this the best way?
Here is what I did:
class VisitInfoViewModel : ViewModelBase
{
List<ServiceType> serviceTypes;
List<ServiceType> allServiceTypes;
public VisitInfoViewModel()
{
ServiceCategories = ServiceCategory.Categories;
allServiceTypes = ServiceType.ServiceTypes;
}
public List<ServiceCategory> ServiceCategories { get; set; }
public List<ServiceType> ServiceTypes
{
get
{
return serviceTypes;
}
}
public ServiceCategory SelectedServiceCategory
{
get { return null; }
set
{
serviceTypes = allServiceTypes.FindAll(st => st.ServiceCategoryGuid.Equals(value.Guid));
OnPropertyChanged("ServiceTypes");
}
}
}
and MainWindow.xaml snippet
<ListBox ItemsSource="{Binding Path=VisitInfo.ServiceCategories}"
SelectedItem="{Binding Path=VisitInfo.SelectedServiceCategory}"
ItemTemplate="{StaticResource listBoxTemplate}"
Height="112"
HorizontalAlignment="Left"
Margin="6,30,0,0"
Name="lbxServiceCategory"
VerticalAlignment="Top"
Width="366" />
<ListBox ItemsSource="{Binding Path=VisitInfo.ServiceTypes}"
ItemTemplate="{StaticResource listBoxTemplate}"
HorizontalAlignment="Left"
Margin="6,0,0,19"
Name="lbxServiceType"
Width="366"
Height="121"
VerticalAlignment="Bottom" />
also, why shouldn't I just add an EventHandler for selectedItemChanged on my listBox?
It seems so much simpler and clearer to use the event handler.
I think it is because if I did that it would no longer by MVVM... is that correct?
What would you do and what are the best practices?
What you are doing is mostly fine - though I would personally make the SelectedServiceCategory a "real" property (with a value that's saved).
The difference with MVVM, and doing it in code behind, is that you're working with data. If you make the "Current Category" change the types, then you're working purely with the data, and not worrying about the UI at all. You can change the category by any mechanism, and the UI will always stay up to date.
I, personally, would suggest writing this more like so:
class VisitInfoViewModel : ViewModelBase
{
List<ServiceType> allServiceTypes;
public VisitInfoViewModel()
{
ServiceCategories = ServiceCategory.Categories;
allServiceTypes = ServiceType.ServiceTypes;
}
// This can use a private setter...
public IEnumerable<ServiceCategory> ServiceCategories { get; private set; }
private ServiceCategory currentCategory;
public ServiceCategory CurrentServiceCategory
{
get { return this.currentCategory; }
set
{
if (this.currentCategory != value)
{
this.currentCategory = value;
ServiceTypesInCurrentCategory = allServiceTypes.Where(st => st.ServiceCategoryGuid.Equals(this.currentCategory.Guid));
OnPropertyChagned("CurrentServiceCategory");
OnPropertyChanged("ServiceTypes");
}
}
}
public IEnumerable<ServiceType> ServiceTypesInCurrentCategory { get; private set; }
}
This provides complete freedom to change the CurrentServiceCategory in code or via Xaml, without any event handlers. It also makes your ViewModel completely data related - you don't know or care what is being used to display this - as long as you have something in your View that sets the CurrentServiceCategory, everything stays synchronized correctly.
also, why shouldn't I just add an EventHandler for selectedItemChanged on my listBox? It seems so much simpler and clearer to use the event handler. I think it is because if I did that it would no longer by MVVM... is that correct?
You can do that, but it's typically a violation of MVVM at this point. The main issue is that you'd be coupling the implementation to that event handler - by doing this, you're basically "locking in" the behavior based on your code in your View for this specific implementation of the View. By keeping it "pure" in terms of MVVM, you're View is free to change (ie: maybe you want to switch to a combobox for the ServiceCategories someday) without touching your ViewModel code at all...

How to bind multiple selection of listview to viewmodel?

I am implementing a listview, and a button next to it. I have to be able that when i select multiple items in a listview, and then click on a button, then the selected items are put into a list. But my question is , how do i bind the selected items towards the viewmodel?
I changed my selectionmode to multiple. But then, do i just have to do:
SelectedItem={Binding path= selectedItems}
and then make in my viewmodel a property selectedItems, and it will set these items i have selected? Or what is the right solution to do this?
Like Doctor has already pointed out, you can bind SelectedItems to XAML CommandParameter
After a lot of digging and googling, I have finally found a simple solution to this common issue.
To make it work you must follow ALL the following rules:
Following Ed Ball's suggestion', on you XAML command databinding, define CommandParameter property BEFORE Command property. This a very time-consuming bug.
Make sure your ICommand's CanExecute and Execute methods have a parameter of object type. This way you can prevent silenced cast exceptions that occurs whenever databinding CommandParameter type does not match your command method's parameter type.
private bool OnDeleteSelectedItemsCanExecute(object SelectedItems)
{
// Your goes here
}
private bool OnDeleteSelectedItemsExecute(object SelectedItems)
{
// Your goes here
}
For example, you can either send a listview/listbox's SelectedItems property to you ICommand methods or the listview/listbox it self. Great, isn't it?
Hope it prevents someone spending the huge amount of time I did to figure out how to receive SelectedItems as CanExecute parameter.
It's kind of tricky to do this Mutliple Selection in MVVM, because the SelectedItems property isn't a Dependency Property. However, there are some tricks you can use. I found this triology of blog posts that describe the matter in some details and provide some useful solutions.
Part I
Part II
Part III
Hope this helps
If you are using System.Windows.Interactivity and Microsoft.Expression.Interactions already, here is a workaround without any other code/behaviour to mess around. If you need these, it can be download from here
This workaround make use of interactivity event trigger and interactions set property mechanism in above assemblies.
Additional namespace declaration in XAML
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
XAML:
<ListView Name="MyListView" ItemsSource="{Binding ModelList}" DisplayMemberPath="Name" Grid.Column="0">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<ei:ChangePropertyAction TargetObject="{Binding Mode=OneWay}" PropertyName="SelectedItems" Value="{Binding Path=SelectedItems, ElementName=MyListView}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ListView>
View Model:
public class ModelListViewModel
{
public ObservableCollection<Model> ModelList { get; set; }
public ObservableCollection<Model> SelectedModels { get; set; }
public ModelListViewModel() {
ModelList = new ObservableCollection<Model>();
SelectedModels = new ObservableCollection<Model>();
}
public System.Collections.IList SelectedItems {
get {
return SelectedModels;
}
set {
SelectedModels.Clear();
foreach (Model model in value) {
SelectedModels.Add(model);
}
}
}
}
In example above, your ViewModel will pick up the selected items whenever the selection on ListView changed.
What you can do is you can handle the Button_Click(...) in your code-behind. Then in that code-behind method you can create a List of selected items by iterating over the selected items of the listView.
Since it is allowed to access the ViewModel from the View you can now call a method on your ViewModel and pass the list of selected items as a parameter.
I'm not sure if this would also work with Bindings only, however it is not bad practice to use code-behind as well.
Example Code:
public void Button_Click(object sender, EventArguments arg)
{
List<ListViewItem> mySelectedItems = new List<ListViewItem>();
foreach(ListViewItem item in myListView.SelectedItems)
{
mySelectedItems.Add(item);
}
ViewModel.SomeMethod(mySelectedItems);
}
EDIT
Here is a minimalist example, XAML:
<DataTemplate
x:Key="CarTemplate"
DataType="{x:Type Car}">
</DataTemplate>
<ListView x:Name="myListView"
ItemsSource="{Binding Path=Cars}"
ItemTemplate="{StaticResource CarTemplate}">
</ListView>
CODE-BEHIND:
public void Button_Click(object sender, EventArguments arg)
{
List<Car> mySelectedItems = new List<Car>();
foreach(Car item in myListView.SelectedItems)
{
mySelectedItems.Add(item);
}
ViewModel.SomeMethod(mySelectedItems);
}
Unfortunately the SelectedItems is a read only not bindable property.
I found a lot of help from this article How to Databind to a SelectedItems property in WPF
If you are using Metro/WinRT you may want to look at the WinRTXXAMLToolkit as it offers a bindable SelectedItems dependency property as one of its extensions.
You can't bind, but you can send to Command as an CommandParameter.
As a slight variation on Christian's post, I implemented similar code using the ListView.SelectionChanged event. Instead of calling a method on the ViewModel, I set a property called SelectedItems:
public void ListView_SelectionChanged( object s, SelectionChangedEventArgs e ) {
List<Car> mySelectedItems = new List<Car>();
foreach( Car item in myListView.SelectedItems )
mySelectedItems.Add(item);
ViewModel.SelectedItems = mySelectedItems;
}
This way, ViewModel.SelectedItems is available for any command you might have in your ViewModel and it can be used for data binding (if you turn it into an ObservableCollection).
I did a solution for this, to me this was simple enough.
<ListBox ItemsSource="{Binding ListOfModel}" x:Name="ModelList"
SelectedItem="{Binding SelectedModel, Mode=TwoWay}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding ExecuteListBoxSelectionChange}" CommandParameter="{Binding ElementName=ModelList}">
</i:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
Then in the viewmodel:
public ICommand ExecuteListBoxSelectionChange { get; private set; }
ExecuteListBoxSelectionChange = DelegatingCommand<ListBox>.For(ListBoxSelectionChnageEvent).AlwaysEnabled();
SelectedModels is the list where I wanted the selection to be filled.
private void ListBoxSelectionChnageEvent(ListBox modelListBox)
{
List<ModelInfo> tempModelInfo = new List<ModelInfo>();
foreach(ModelInfo a in modelListBox.SelectedItems)
tempModelInfo.Add(a);
SelectedModels = tempModelInfo;
}

wpf databinding timing issue

Something is wrong with my binding. But I can't find it
I have a status type control (UserControl) that has an ItemsControl with binding that relies on a ViewModelBase object which provides list of BrokenRules, like so:
<ItemsControl ItemsSource="{Binding BrokenRules}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock>
<Hyperlink Foreground="Red" >
<TextBlock Text="{Binding Description}" />
</Hyperlink>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The binding works the way I want to, in the sense that any and all broken rules Descriptions are displayed. A rule is pretty much just a description and a delegate that is executed when the rule is told to validate itself.
Most rules have Descriptions that are known up front, before the rule is asked to validate itself. For example, "Name is not valued" is a fine description of what went wrong if the validation delegate !Name.IsNullOrEmptyAfterTrim() fails.
The problem comes with one particular rule, which checks for duplicate names. If the dup check fails, I want to be able to say what the duplicated value is, which is impossible to know up front. So the rule needs to update the Description when the validation delegate is executed.
When I unit test or leave a debug trace in the validation delegate, the broken rule description is updated. But when I run the app, the broken rule description is what is was before it was updated.
I am therefore guessing my binding is not correct. Can anyone suggest what the problem / fix is?
Cheers,
Berryl
UPDATE ====================
This is code from my ViewModelBase class:
private readonly List<RuleBase> _rules = new List<RuleBase>();
// inheritors add rules as part of construction
protected void _AddRule(RuleBase rule) { _rules.Add(rule); }
public ObservableCollection<RuleBase> BrokenRules { get { return _brokenRules; } }
protected ObservableCollection<RuleBase> _brokenRules;
public virtual IEnumerable<RuleBase> GetBrokenRules() {
return GetBrokenRules(string.Empty);
}
public virtual IEnumerable<RuleBase> GetBrokenRules(string property) {
property = property.CleanString();
_brokenRules = new ObservableCollection<RuleBase>();
foreach (var r in _rules) {
// Ensure we only validate this rule
if (r.PropertyName != property && property != string.Empty) continue;
var isRuleBroken = !r.ValidateRule(this);
if (isRuleBroken) _brokenRules.Add(r);
return _brokenRules;
}
You must ensure that the BrokenRules observable collection instance doesn't change, your code on the View Model should look something like:
public ObservableCollection<BrokenRule> BrokenRules
{
get;
set;
}
private void ValidateRules()
{
// Validation code
if (!rule.IsValid)
{
this.BrokenRules.Add(new BrokenRule { Description = "Duplicated name found" });
}
}
If for example, you do something like this instead:
this.BrokenRules = this.ValidateRules();
You would be changing the collection which is bound to the ItemsControl without notifying it and changes won't reflect on UI.

In C# (or in C# with WPF), how would I build a checkbox at run time?

In C# (or in C# with WPF), how would I build a checkbox at run time?
I would I be able to query the check box to see if the user clicked on it?
In other words, suppose I have a "grid" on which I want to have displayed some checkboxes. But I do not know how many checkboxes to display. I suppose I could (in WPF) fill the grid with checkboxes at design time and mark them as hidden (or visibly == false) and then show them at run time. But I was hoping there was a more elegant way to do this.
There are several ways to do this in WPF. A quick and dirty approach would be to do something like this:
<StackPanel x:Name="CheckBoxes" />
Then in your code behind do:
for (int i=0; i < 10; i++) {
this.CheckBoxes.Children.Add(new CheckBox());
}
But while at first glance it looks simple, this makes it somewhat of a pain to work with in the long run. Instead, a better solution would be to have a class that has a boolean property such as:
// this should really implement INotifyPropertyChanged but
// we'll ignore that for now...
public class SelectableThing {
public bool IsSelected {
get;
set;
}
public string Description {
get;
set;
}
}
Then in your XAML, you would have a bindable control such as ItemsControl:
<ItemsControl x:Name="CheckBoxes">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsSelected, Mode=TwoWay}"
Content="{Binding Description}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Then in your code behind you could create a collection of these SelectableThing's and set them as the ItemsSource.
private SelectableThing[] things;
// where you do this is up to you really
private void Window_Load(object sender, RoutedEventArgs e) {
things = new SelectableThing[] {
new SelectableThing("First Thing"),
new SelectableThing("Second Thing"),
new SelectableThing("Third Thing")
};
CheckBoxes.ItemsSource = things;
}
In an event handler or something like that, eventually a method that gets called, you could do this. Let's say your Canvas is called myCanvas.
var cb = new CheckBox { //... set the properties, e.g.:
Checked = true, Content = "Check me" };
// do whatever you like to do with your newly created CheckBox
myCanvas.Children.Add(cb);
Hope this helps; of course you can do this inside a loop. If you need to hold a specific set of references to the created CheckBoxes be aware of that or use the Tag Property to identify these special CheckBoxes. Also, you could check myCanvas.Children for CheckBoxes.

Resources