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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With