Synchronize DataGrid and DataForm in Silverlight 3 - silverlight

I've been banging my head against the wall for a couple of days on this and it's time to ask for help.
I've got a DataGrid and DataForm on the same UserControl. I'm using an MVVM approach so there is a single ViewModel for the UserControl. That ViewModel has a couple of properties that are relevant to this discussion:
public ObservableCollection<VehicleViewModel> Vehicles { get; private set; }
public VehicleViewModel SelectedVehicle
{
get { return selectedVehicle; }
private set
{
selectedVehicle = value;
OnPropertyChanged( "SelectedVehicle" );
}
}
In the XAML I've got the DataGrid and DataForm defined as follows:
<data:DataGrid SelectionMode="Single"
ItemsSource="{Binding Vehicles}"
SelectedItem="{Binding SelectedVehicle, Mode=TwoWay}"
AutoGenerateColumns="False"
IsReadOnly="True">
<dataFormToolkit:DataForm CurrentItem="{Binding SelectedVehicle}" />
So as the SelectedItem changes on the DataGrid it should push that change back to the ViewModel and when the ViewModel raises the OnPropertyChanged the DataForm should refresh itself with the information for the newly-selected VehicleViewModel. However, the setter for SelectedVehicle is never being called and in the Output window of VS I'm seeing the following error:
System.Windows.Data Error: ConvertBack cannot convert value 'xxxx.ViewModel.VehicleViewModel' (type 'xxxx.ViewModel.VehicleViewModel'). BindingExpression: Path='SelectedVehicle' DataItem='xxxx.ViewModel.MainViewModel' (HashCode=31664161); target element is 'System.Windows.Controls.DataGrid' (Name=''); target property is 'SelectedItem' (type 'System.Object').. System.MethodAccessException: xxxx.ViewModel.MainViewModel.set_SelectedVehicle(xxxx.ViewModel.VehicleViewModel)
It sounds like it's having a problem converting from a VehicleViewModel to an object (or back again), but I'm confused as to why that would be (or even if I'm on the right track with that assumption). Each row/item in the DataGrid should be a VehicleViewModel (because the ItemsSource is bound to an ObservableCollection of that type), so when the SelectedItem changes it should be dealing with an instance of VehicleViewModel.
Any insight would be appreciated.

Your setter on the public VehicleViewModel SelectedVehicle is private so the DataGrid cannot modify it using the TwoWay mode. Your setter will need to be public.

Related

WPF MVVM ListBox/ComboBox ItemsSource won't update UI from ViewModel

i have a container such as a listbox, combobox etc that its ItemsSource property is bound to an observable collection in my view model.
When i'm trying to add/remove items from the collection via some method in my VM it won't reflect in the UI,
The only way the UI would actually refresh is if i assign the collection a new value (i.e another collection with the relevant data) which forces him to re-bind the whole collection.
maybe i'm missing/don't understand something about the collection binding issue, either way if someone has a solution/good explanation/both it would be great.
here is a sample from my View(in this case its a listbox)
<ListBox
Grid.Row="9"
Grid.Column="1"
Grid.ColumnSpan="3"
Width="200"
Height="200"
ItemsSource="{Binding PreSavedRecordingScheduleList,UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding SelectedPreSavedRecordingSchedule,UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="Display"/>
and here is my ViewModel:
private ObservableCollection<ScheduledRecordingObject> m_PreSavedRecordingScheduleList;
PreSavedRecordingScheduleList = new ObservableCollection<ScheduledRecordingObject>();
public ObservableCollection<ScheduledRecordingObject> PreSavedRecordingScheduleList
{
get
{
return m_PreSavedRecordingScheduleList;
}
set
{
m_PreSavedRecordingScheduleList = value;
OnPropertyChanged("PreSavedRecordingScheduleList");
}
}
ScheduledRecordingObject also implements INotifyPropertyChanged.
viewmodel
public ObservableCollection<yourType> MyItemsSource {get;set}
initialize once in contructor and use clear, add and remove to alter it
view
<ListBox ItemsSource="{Binding MyItemsSource}"/>
just be sure that the right DataContext is set.
thats how it should look in your code
EDIT:
some hints to your posted code:
//remove the UpdateSourceTrigger=PropertyChanged - makes no sense the Mode is OneWay anyway :)
ItemsSource="{Binding PreSavedRecordingScheduleList}"
//the following line should just called once and at best in ctor
//but the binding will of course work too when you assign a new collection
PreSavedRecordingScheduleList = new ObservableCollection<ScheduledRecordingObject>();
all in all your code looks good, and if the viewmodel is the Datacontext of your Listbox then it should work. let me know what Snoop is showing :)
Remove the OnPropertyChanged("PreSavedRecordingScheduleList"); from the ObservableCollection. Actually you don't need a backing field. Attach the CollectionChanged event on the ObservableCollection, something like this
1- Inside the ViewModel constructor attach the event CollectionChanged
PreSavedRecordingScheduleList = new ObservableCollection<ScheduledRecordingObject>();
PreSavedRecordingScheduleList.CollectionChanged += PreSavedRecordingScheduleList_CollectionChanged;
2- Inject the OnPropertyChanged("PreSavedRecordingScheduleList") in the event handler
void PreSavedRecordingScheduleList_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
OnPropertyChanged("PreSavedRecordingScheduleList");
}

Binding to DependencyProperty in UserControl

I have a form with two different UserControls - one that contains a Telerik RadGridView and the other that that contains a Telerik DataForm.
The grid usercontrol is bound to a ViewModel that includes a property that exposes the Items collection that the grid is bound to.
When I bind the form to that property, everything works fine.
But I need to access additional info in the form control that really doesn't belong in the grid control's viewmodel.
So I thought I'd add a property to the form usercontrol, and bind it to the items collection:
<local:FormControl x:Name="formControl"
ItemsSource="{Binding items}"
/>
In the form's code-behind, I added a normal property:
private object itemsSource;
public object ItemsSource
{
get { return this.itemsSource; }
set { this.itemsSource = value; }
}
And this didn't work, of course. I got errors about having to use a DependencyProperty. Which I thought was reassuring - the page was actually trying to bind to the property I thought it should.
So I converted this to a DependencyProperty:
public static DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(object), typeof(FormControl));
public object ItemsSource
{
get { return GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
That compiled and ran without errors. Except, of course, that the control's viewmodel didn't have any items. So next was to try to pass the ItemsSource property to the form control's viewmodel:
public FormControl()
{
InitializeComponent();
this.DataContext = new FormControlVM(this.ItemsSource);
...
And this didn't work. ItemsSource was null when FormControl() was constructed. So I added a setItemsSource() method to the viewmodel, and called it in the ItemsSource property's set function. This didn't work, either. The xaml is apparently binding to the property, but it seems to do it without calling the property's set function.
So I decided to listen to the ValueChanged event, on the DependencyProperty:
public FormControl()
{
InitializeComponent();
this.DataContext = new FormControlVM();
DependencyPropertyDescriptor prop =
DependencyPropertyDescriptor.FromProperty(FormControl.ItemsSourceProperty, this.GetType());
prop.AddValueChanged(this, delegate
{
FormControlVM formControlVM = (FormControlVM)this.DataContext;
formControlVM.setItemsSource(this.ItemsSource);
});
...
And it's still not working. The ValueChanged delegate never seems to get called.
This seems like it should be a simple thing, but I've not been able to find examples on-line, and I've tried every combination I can conceive of.
Any ideas as to how I should handle this?
============ Additional info on the binding ============
Will was asking for info on the xaml that does the binding.
If I put this in page that contains the user control, binding the user control to the viewmodel of the page:
<local:FormControl x:Name="formControl"
Grid.Column="2"
DataContext="{Binding}"
/>
And then this in the user control, binding the form in the user control to the itemsCollection of the page's viewmodel:
<telerik:RadDataForm
ItemsSource="{Binding itemsCollection}"
Header="View Item:"
CommandButtonsVisibility="None"
AutoGenerateFields="False"
/>
Then everything works fine.
But the problem is that I can't bind the user control to the viewmodel of the page. I need to have the user control's viewmodel expose information that the page's viewmodel should not be seeing.
And I can't have the form in the user control reaching outside of the user control, binding to a property on the page's viewmodel. It's a violation of encapsulation, that will make using the user control on a different page much more complicated, and severely limit how I might modify the internals of the control in the future. (The page should not know anything about the controls within the user control, the controls within the user control should not know anything about the page.)
When I include the user control on a page, I want to bind the user control to a property of the page's viewmodel, and I want any controls within the user control to bind to properties of the user control, or of the user control's viewmodel.
I'd think this was a fairly common occurrence. But I've not been able to find any examples of how it might be done.
To restate the problem: I have a UserControl, that contains an embedded Telerik DataForm control. I need to bind the ItemsSource property of the embedded DataForm to a property of the DataContext of the page on which my UserControl is placed.
If the UserControl did not have its DataContext set, it would inherit the DataContext of the page, and I could easily bind the embedded DataForm's ItemsSource property to a property of it, but the UserControl has it's own DataContext.
If the UserControl was written to be used only on this page, I could bind the embedded DataForm's ItemsSource property to a property of the page, using RelativeSource binding. But this UserControl is intended to be used in a number of places, and cannot have silent dependencies on properties outside of the UserControl. I need to make the dependency explicit.
Creating a DependencyProperty on the UserControl is the right approach, but there's no need to try to replicate the property in the UserControl's viewmodel.
What I need to do is to
1: Add a DependencyProperty to the UserControl:
public QueryableCollectionView ItemsSource
{
get { return (QueryableCollectionView)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(QueryableCollectionView), typeof(FormControl));
Note: I do not need to implement a call-back function.
2: Bind the embedded DataForm's ItemsProperty to this new DependencyProperty of the UserControl, using RelativeSource binding:
<UserControl
...>
<telerik:RadDataForm
ItemsSource="{Binding Path=ItemsSource, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
/>
</UserControl>
3: Make the viewModel of the page visible, with the proper type (DataContext has type object):
public partial class MainWindow : Window
{
public MainWIndow()
{
this.InitializeComponent();
this.DataContext = new MainWindowVM();
}
public MainWindowVM viewModel
{ get { return this.DataContext as MainWindowVM; } }
}
4: On the page, bind the UserControl's new ItemsProperty DependencyProperty to the appropriate property of the page's viewmodel, again using RelativeSource binding:
<Window
...>
<local:FormControl
ItemsSource="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type telerik:RadPane}},Path=viewModel.items}"
/>
</Window>

How can I realize SelectionChanged in MVVM ListBox Silverlight

The ListBox control does not implement a Command property. I have to attach some functionality to the SelectionChanged event. Somebody knows how can I do it? Please help me
I prefer using a binding to the SelectedItem and implementing any functionality in the setting of the binding property.
<ListBox ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}" />
...
public class ViewModel
{
public IEnumerable<Item> Items { get; set; }
private Item selectedItem;
public Item SelectedItem
{
get { return selectedItem; }
set
{
if (selectedItem == value)
return;
selectedItem = value;
// Do logic on selection change.
}
}
}
This is the way where You can Reach the Selection changed events in Your MVVM Application
First Of all i tell you that Command Property only work in Button now we have to Explicitly
binding that property in our Selection Changed event like List box or combo box
in Your XMAL file
<ListBox Name="MyListBox" ItemsSource="{Binding ListItems}" Height="150" Width="150" Margin="281,32,-31,118">
<Local:Interaction.Triggers>
<Local:EventTrigger EventName="SelectionChanged">
<Local:InvokeCommandAction Command="{Binding MyCommand}" CommandParameter="{Binding ElementName=MyListBox,Path=SelectedItem}"/>
</Local:EventTrigger>
</Local:Interaction.Triggers>
</ListBox>
for this you have to add dll Syatem.Windows.Interactivity
now u have to add references in your xaml file namespace like
xmlns:Local="clr-namespace:System.Windows.Interactivityassembly=System.Windows.Interactivity"
in your ViewModel Class you have to define your Command in Con structure
public ViewModel123()
{
MyCommand = new RelayCommand<string>(TestMethod);
}
now create the TestMethod method which can handle the selection changed event
private void TestMethod(string parameter)
{
MessageBox.Show(parameter);
}
i hope this may help u.
Basically you have a few options:
Use the property SelectedItem of ListBox to bind to a property in the backend (ie in view model) and perform logic in the setter as described by Cameron MacFarland.
Use a third party library that has a generic event to command behavior like in the link posted by Pedro Lamas.
If you don't want to use third party libraries or writing logic inside property setter is somehow unacceptable you can create your own behavior for ListBox control. It would subscribe to control's SelectionChanged event and execute a command (the command could be a dependency property exposed by the behavior).
Think this post from Laurent Bugnion will help you solve the problem:
http://geekswithblogs.net/lbugnion/archive/2010/05/19/handling-datagrid.selecteditems-in-an-mvvm-friendly-manner.aspx
The post above mentions the DataGrid but I do think it will work with the ListBox too!
Best regards and Happy New Year!! :)
I would suggest using RelayCommand. Either use the MVVM Light Toolkit or just use the RelayCommand and CommandManager classes from Josh Smith's implementations. I personally use just the two classes, so I don't need the entire toolkit.
While this will definitely work, there might be an easier way depending on what you are doing. It might just be easier to bind an object to the SelectedValue of the ListBox and listen for that value to change.

How to pass the selectedItem of a listbox to the View Model

This is a running question that I have updated to hopefully be a little more clear.
In short what I am trying to accomplish is pass a property from a listbox selected item to the viewmodel so that this property can be used within a new query. In the code below the Listbox inherits databinding from the parent object. The listbox contains data templates (user controls) used to render out detailed results.
The issue I am having is that within the user control I have an expander which when clicked calls a command from the ViewModel. From what I can see the Listbox object is loosing it's data context so in order for the command to be called when the expander is expanded I have to explicitly set the datacontext of the expander. Doing this seems to instantiate a new view model which resets my bound property (SelectedItemsID) to null.
Is there a way to pass the selected item from the view to the viewmodel and prevent the value from being reset to null when a button calls a command from within the templated listbox item?
I realize that both Prism and MVVMLite have workarounds for this but I am not familiar with either framework so I don't know the level of complexity in cutting either of these into my project.
Can this be accomplished outside of Prism or MVVMLite?
original post follows:
Within my project I have a listbox usercontrol which contains a custom data template.
<ListBox x:Name="ResultListBox"
HorizontalAlignment="Stretch"
Background="{x:Null}"
BorderThickness="0"
HorizontalContentAlignment="Stretch"
ItemsSource="{Binding SearchResults[0].Results,
Mode=TwoWay}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
SelectionChanged="ResultListBox_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<dts:TypeTemplateSelector Content="{Binding}" HorizontalContentAlignment="Stretch">
<!-- CFS Template -->
<dts:TypeTemplateSelector.CFSTemplate>
<DataTemplate>
<qr:srchCFS />
</DataTemplate>
</dts:TypeTemplateSelector.CFSTemplate>
<!-- Person Template -->
<dts:TypeTemplateSelector.PersonTemplate>
<DataTemplate>
<qr:srchPerson />
</DataTemplate>
</dts:TypeTemplateSelector.PersonTemplate>
<!-- removed for brevity -->
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
SelectionChanged calls the following method from the code behind
private void ResultListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (((ListBox)sender).SelectedItem != null)
_ViewModel.SelectedItemID = (((ListBox)sender).SelectedItem as QueryResult).ID.ToString();
this.NotifyPropertyChanged(_ViewModel.SelectedItemID);//binds to VM
}
Within the ViewModel I have the following property
public string SelectedItemID
{
get
{
return this._SelectedItemID;
}
set
{
if (this._SelectedItemID == value)
return;
this._SelectedItemID = value;
}
}
the listbox template contains a custom layout with an expander control. The expander control is used to display more details related to the selected item. These details (collection) are created by making a new call to my proxy. To do this with an expander control I used the Expressions InvokeCommandAction
<toolkit:Expander Height="auto"
Margin="0,0,-2,0"
Foreground="#FFFFC21C"
Header="View Details"
IsExpanded="False"
DataContext="{Binding Source={StaticResource SearchViewModelDataSource}}"
Style="{StaticResource DetailExpander}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Expanded">
<i:InvokeCommandAction Command="{Binding GetCfsResultCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
Within the ViewModel the delegate command GetCFSResultCommandExecute which is called is fairly straight forward
private void GetCfsResultCommandExecute(object parameter)
{
long IdResult;
if (long.TryParse(SelectedItemID, out IdResult))
{
this.CallForServiceResults = this._DataModel.GetCFSResults(IdResult);}
The issue I am experiencing is when selecting a listbox Item the selectionchanged event fires and the property SelectedItemID is updated with the correct id from the selected item. When I click on the expander the Command is fired but the property SelectedItemID is set to null. I have traced this with Silverlight-Spy and the events are consistent with what you would expect when the expander is clicked the listbox item loses focus, the expander (toggle) gets focus and there is a LeftMouseDownEvent but I cannot see anything happening that explains why the property is being set to null. I added the same code used in the selection changed event to a LostFocus event on the listboxt item and still received the same result.
I'd appreciate any help with understanding why the public property SelectedItemID is being set to null when the expander button which is part of the listbox control is being set to null. And of course I would REALLY appreciate any help in learning how prevent the property from being set to null and retaining the bound ID.
Update
I have attempted to remove the datacontext reference from the Expander as this was suggested to be the issue. From what I have since this is a data template item it "steps" out of the visual tree and looses reference to the datacontext of the control which is inherited from the parent object. If I attempt to set the datacontext in code for the control all bindings to properties are lost.
My next attempt was to set the datacontext for the expander control within the constructor as
private SearchViewModel _ViewModel;
public srchCFS()
{
InitializeComponent();
this.cfsExpander.DataContext = this._ViewModel;
}
This approach does not seem to work as InvokeCommandAction is never fired. This command only seems to trigger if data context is set on the expander.
thanks in advance
With this line you create a new SearchViewModelDataSource using its default constructor.
DataContext="{Binding Source={StaticResource SearchViewModelDataSource}}"
I guess this is why you find null because this is the default value for reference type.
You can resolve the issue by setting DataContext to the same instance used to the main controll (you can do it by code after all components are initialized).
Hope this help!
Edit
I don't think that binding may be lost after setting datacontext from code. I do it every time I need to share something between two or more model.
In relation to the code you've written :
private SearchViewModel _ViewModel;
public srchCFS()
{
InitializeComponent();
this.cfsExpander.DataContext = this._ViewModel;
}
Instead of using this.cfsExpander you can try to use the FindName method. Maybe this will return you the correct instance.
object item = this.FindName("expander_name");
if ((item!=null)&&(item is Expander))
{
Expander exp = item as Expander;
exp.DataContext = this._ViewModel;
}
Try if its work for you.
Of course, this._ViewModel has to expose a property of type ICommand named GetCfsResultCommand but I think this has been already done.
While this was a hacky approach I found an intermediate solution to get the listbox item value to the view model. I ended up using the selection changed event and passing the value directly to a public property wihtin my view model. Not the best approach but it resolved the issue short term
private void ResultListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (((ListBox)sender).SelectedItem != null)
_ViewModel.SelectedItemID = (((ListBox)sender).SelectedItem as QueryResult).ID.ToString();
MySelectedValue = (((ListBox)sender).SelectedItem as QueryResult).ID.ToString();
this.NotifyPropertyChanged(_ViewModel.SelectedItemID);
}
For this to fire I did have to also setup a property changed handler within the view to push the change to the VM. You can disregard the MySelectedValue line as it is secondary code I have in place for testing.
For those intereted the generic property changed handler
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}

MVVM: Binding to ListBox.SelectedItem isn't being updated?

How do I bind a view model property to the ListBox.SelectedItem property?
I have created a simple MVVM demo to try to figure this one out. My view model has these properties:
private ObservableCollection<DisneyCharacter> p_DisneyCharacters;
public ObservableCollection<DisneyCharacter> DisneyCharacters
{
get { return p_DisneyCharacters; }
set
{
p_DisneyCharacters = value;
base.FirePropertyChangedEvent("DisneyCharacters");
}
}
private DisneyCharacter p_SelectedItem;
public DisneyCharacter SelectedItem
{
get { return p_SelectedItem; }
set
{
p_SelectedItem = value;
base.FirePropertyChangedEvent("SelectedItem");
}
}
I want to bind the SelectedItem property to the item selected in the list box. Here is the XAML for the list box:
<ListBox ItemTemplate="{StaticResource MasterTemplate}"
ItemsSource="{Binding Path=DisneyCharacters}"
SelectedItem="{Binding Path=Selectedtem, Mode=TwoWay}"
HorizontalAlignment="Stretch" />
Here is my problem: The view model SelectedItem property isn't being updated when I change the selection in the list box.
I did a test where I temporarily replaced the view model SelectedItem property with a SelectedIndex property, and I bound that to the ListBox.SelectedIndex property. That property updated fine--it's just the SelectedItem property that I can't get to work.
So, how do I fix the SelectedItem binding? Thanks for your help.
Well, there it is, big as life. In the XAML I was binding to a view model property called:
Selectedtem
^^
Unfortunately, the actual name is SelectedItem. So this code actually works--I solved the problem early this afternoon and then spent the rest of the afternoon and all evening scouring the web, before I noticed the spelling error.
My wife told me at 3:00 this afternoon, "You know, it's going to turn out to be something small." And so it did--a missing letter I. Well, at least I can go to bed now.

Resources