PRISM-MVVM, ItemsControl problem with View injection - silverlight

I need to display multiple instances of a basketDetailsView.xaml within a region placed in basketView.xaml, but I'm getting the following errormessage when i debug my code:
"An exception occurred while creating a region with name 'basketRegion'. The exception was: System.InvalidOperationException: ItemsControl's ItemsSource property is not empty. This control is being associated with a region, but the control is already bound to something else. If you did not explicitly set the control's ItemSource property, this exception may be caused by a change in the value of the inherited RegionManager attached property"
The basketView XAML contains an ItemsControl tag defined like this
<ItemsControl x:Name="basketItemsControl"cal:RegionManager.RegionName="basketRegion"/>
The view also has a listbox where I can uncheck/check the BasketDetailsViews I want to look at:
<ListBox x:Name="basketListBox" ItemsSource="{Binding basket}" MinWidth="200">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox commands:Checked1.Command="{Binding DataContext.CheckCommand,ElementName=basketListBox}" Content="{Binding basketName}" ></CheckBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox
When I run without debugging it executes fine and I can pop in/out the different basketDetailsViews, but when debugging the above mentioned error shows. What Am i doing wrong?
EDIT:
Public Sub AddCageDetailsView(ByVal BasketName As String)
Dim basketRegion = _RegionManager.Regions("basketRegion")
Dim view = _Container.Resolve(Of basketDetailsView)()
Dim viewmodel = _Container.Resolve(Of basketDetailsViewModel)()
view.ApplyModel(viewmodel)
basketRegion.Add(view)
End Sub
So basketRegion is the region in my ItemsControl as specified above. This region is supposed to hold my basketDetailsViews..

Related

Binding inside ContentControl not working

I'm building a graphical designer, based upon an article by Sukram in CodeProject. I'm now trying to extend it so that each item on the canvas binds to a different ViewModel object - i.e. I'm setting the DataContext for each item.
Every item on the designer is actually a ContentControl, into which is placed a different template (based upon which toolbox item was dragged onto the canvas). So I have a template containing a TextBox, and I have a ViewModel object containing a Name property, and I bind the Text property of the TextBox to the Name property of the ViewModel, and ... nothing. I've checked the visual tree with Snoop, and it confirms that the DataContext of the TextBox is the ViewModel object. Yet the TextBox remains empty. And if I modify the (empty) Text in the TextBox, the Name property in the ViewModel does not change. So it looks like the binding is not being applied (or has been removed somehow).
I've found a few posts which talk about the ContentControl messing around with the DataContext and Content properties, but I'm not sure how applicable they all are. The code sets the ContentControl.Content as follows:
newItem = new ContentControl();
ControlTemplate template = toolbox.GetTemplate();
UIElement element = template.LoadContent() as UIElement;
ViewModelItem viewModel = new ViewModelItem() { Name = "Bob" };
newItem.Content = element;
newItem.DataContext = viewModel;
and the XAML for the template is:
<ControlTemplate>
<Border BorderBrush="Black" BorderThickness="1" Width="100">
<TextBox Text={Binding Name}/>
</Border>
</ControlTemplate>
Snoop shows that the TextBox has a DataContext, and if I Delve that DataContext I can see that it has a Name property whose value is "Bob". So why does the TextBox remain empty? Snoop allows me to change that Name property, but the TextBox remains empty.
What am I doing wrong?
A few more details. I've set the VS2010 Debug DataBinding option for the OutputWindow to Verbose, which seems to show that the binding is all being attempted before I set the DataContext. Is it possible that the change to the DataContext is not being recognised?
I've just found this post DataTemplate.LoadContent does not preserve bindings - apparently DataTemplate.LoadContent does not preserve bindings. So it looks like I have to write my own version of LoadContent().
I've realised that the template has come through a XamlWriter, which apparently strips all bindings. This wouldn't be helping.
I've not been able to fix the DataTemplate.LoadContent(), but I realised that I didn't actually need a DataTemplate, since the XamlWriter / XamlReader was already instantiating the UI element that I was after. I found a fix to make the XamlWriter write all the bindings here, and after that it all works.
Thanks for your help.
Maybe you need to tell the binding in the ControlTemplate to look at the TemplatedParent, as is mentioned in this thread?
<TextBox Text="{Binding Path=Name, RelativeSource={RelativeSource TemplatedParent}}"/>
Either that, or try to use a DataTemplate instead.
I can't test this at the moment, so I might just be guessing here.
I would use a DataTemplate, as bde suggests.
You are trying to put some UI on your own data (ViewModel), and this is what Data-Templates are meant for (ControlTemplate is usually what you use if you want to change how e.g. a Button looks).
Change your code to use ContentControl.ContentTemplate with a DataTemplate:
<DataTemplate>
<Border BorderBrush="Black" BorderThickness="1" Width="100">
<TextBox Text={Binding Name}/>
</Border>
</DataTemplate>
Code-behind:
newItem = new ContentControl();
//NOTE: .GetTemplate() needs to return a DataTemplate, and not a ControlTemplate:
newItem.ContentTemplate = toolbox.GetTemplate();
ViewModelItem viewModel = new ViewModelItem() { Name = "Bob" };
newItem.Content = viewModel;
newItem.DataContext = viewModel;

Two way datatable binding in WPF

I have a datagrid (A C1 datagrid, in this case) bound to a property in my View Model. The XAML for the datagrid looks like this:
<c1:C1DataGrid
AutoGenerateColumns="False"
IsReadOnly="False"
Margin="5" Width="auto"
MinWidth="250"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Name="dgNotifAssign"
CanUserAddRows="True"
ItemsSource="{Binding Path=notifCodeSubs.notification_configuration}"
>
<c1:C1DataGrid.Columns>
<c1:DataGridTextColumn
Binding="{Binding Path=user_id}"
Header="Recipient"
VerticalAlignment="Stretch"
SortMemberPath="user_id"
>
The property that it is bound to, in my viewmodel, looks like this:
Public Property notifCodeSubs As dsPeruseFM
Get
If _notifCode Is Nothing Then
_notifCode = New dsPeruseFM
End If
Return _notifCode
End Get
Set(ByVal value As dsPeruseFM)
MsgBox("If you can see this, success!")
End Set
End Property
In the codebehind I create an instance of the viewmodel and set the datacontext of the xaml to that instance, rather simple...
Dim vm As New ctrlAlertNotifyVM
As well as:
ctrlAlertNotifyXML.DataContext = vm
The above configuration compiles and reads data just fine. The grid is populated with all the correct data, etc. The problem comes when I try to add Mode=twoway to the ItemsSource on the datagrid. At that point VS2010 spits out the following error:
A TwoWay or OneWayToSource binding cannot work on the read-only property 'notification_configuration' of type 'PeruseFM.dsPeruseFM'.
I'm quite sure that all of my properties are read/write. And while the set command for this is nothing more than a message box at this point, it doesn't seem like I can even access that.
So the question is... has anybody ever encountered this issue before?
Update, response to question "What does notification_configuration look like?" from sixlettervariables:
Public Function codeChanged(Optional ByVal x As String = "")
If _notifCode Is Nothing Then
_notifCode = New dsPeruseFM
End If
taNotifSubs.fillNotifSubs(notifCode:=x, dataTable:=_notifCode.notification_configuration)
Return _notifCode
End Function
You've shown us that notifCodeSubs is read/write, however, that is not the actual property you've bound to.
From this viewpoint, the error message is fairly self-explanatory:
...read-only property 'notification_configuration'...
Therefore you cannot apply TwoWay binding to that property as an ItemsSource.

ListBox Binding with Global Index

My application has a couple of ObservableCollections, one which is nested within an element of the other. Each contain a number of fields e.g.
ObservableCollectionA (called Listings)
Title
Description
Address
Images As MediaItems
ObservableCollectionB (called MediaItems)
ImageName
Src
DisplayTime
Currently I have been accessing ObservableCollections as follows:
Listings(0).MediaItems(0).ImageName
My application has the main Window display the items from Listings and a UserControl which contains a ListBox which displays the items from MediaItems.
Currently my Window is bound to Listings using code in the New method:
Dim AppLocal As Program = Application.CurrentItem
AppLocal.CurrentItem = 0
Me.DataContext = Listings.Item(AppLocal.CurrentItem)
For the Listings ObservableCollection, the UserControl has a XAML DataContext which references a local method which pulls the records from the nested MediaItems ObservableCollection.
<UserControl.DataContext>
<ObjectDataProvider ObjectType="{x:Type local:ThumbImageLoader}" MethodName="LoadImagesv2" IsAsynchronous="True" />
</UserControl.DataContext>
<Grid x:Name="ThumbListBoxGrid">
<ListBox x:Name="ThumbListBox" ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True" />
</Grid>
The method is here:
Public NotInheritable Class ThumbImageLoader
Public Shared Function LoadImagesv2() As List(Of MediaItems)
Dim AppLocal As Program = Application.Current
Dim ThumbImages As New List(Of MediaItems)
ThumbImages = Listings(AppLocal.CurrentItem).MediaItems
Return ThumbImages
End Function
End Class
Whilst developing the UI layout I have just been binding the first item (0 index). I now want to be able to set AppLocal.CurrentItem from anywhere in the application so the Window and the ListBox are updated.
Ideally I would like it so when the global property index changes, the UI is updated.
How do I do this?
Is there a better way to go about it?
Ben
Ok, I discovered the joy of CollectionView. Offered exactly what I was after and was excrutiatingly easy to implement. I was blown away at not only how easy it was to implement, but I managed to cut out more lines of code than I used to implement it.
I implemented a public CollectionViewSource
Public ListingDataView As CollectionViewSource
In my main Window, I implemeted it as follows:
<CollectionViewSource x:Key="ListingDataView" />
and bound my top-level Grid to it:
<Grid DataContext="{Binding Source={StaticResource ListingDataView}}">
In my Application Startup I set the CollectionView Source
AppLocal.ListingDataView = CType(Application.Current.MainWindow.Resources("ListingDataView"), CollectionViewSource)
AppLocal.ListingDataView.Source = Listings
The next part which impressed me the most was implementing it for my User Control. I remembered the UserControl is inheriting from the main window so it has access to the CollectionView already, so I ditched the separate Class and Method binding in favour for this:
<ListBox ItemsSource="{Binding Path=MediaItems}" VerticalAlignment="Top" IsSynchronizedWithCurrentItem="True" />
Now whene I want to set the Current List Index, I simply call this:
AppLocal.ListingDataView.View.MoveCurrentToPosition(AppLocal.CurrentProperty)
A few milliseconds later, the UI updates automatically.
Done!!
When you want multiple source data (like your observable collection properties and the index for the observable collection) to a single target you should use MultiBinding.
So in your case somethign like this should help...
<ListBox x:Name="ThumbListBox" IsSynchronizedWithCurrentItem="True" >
<ListBox.ItemsSource>
<MultiBinding Converter="{StaticResource CollectionAndIndexCollaborator}">
<Binding Path="Listings" />
<Binding Path="Application.CurrentItem.CurrentItem" />
</MultiBinding>
</ListBox.ItemsSource>
</ListBox>
provided that ....
Your data context is some class that holds the Application object via a property of same name Application and the Listings collection via property of same name Listings.
Your DataContext class and Application class must have INotifyPropertyChanged implemented. It should also raise notifications for Application and Setter of CurrentItem and CurrentItem.CurrentItem properties.
CollectionAndIndexCollaborator.Convert() method returns the same indexed value as the final collection....
return ((ObservableCollection)values[0]).Item(int.Parse(values[1])) ;
where assuming MyListingType is the T of your Listings collection.
This way when anyone changes the global Application.CurrentItem.CurrentItem the multi binding above will get notified and will select the required Listings item.

SL ItemsControl, command on ViewModel not firing from ItemsControl (CheckBox)

I'm using PRISM v2, CAL, SL4 and MVVM and have a delegate command on my ViewModel called CheckCommand. The ItemsControl contains a checkbox and I'm trying to get the items in ItemsControl/Checkbox to fire this command when it's checked - but it's not communication back to the viewmodel!
I think it's because each items 'datacontext' is the individual object the item is bound to, rather than the ViewModel?
- My suspicion is actually correct, cause if I move my DelegateCommand out of the viewmodel and into the class defining the items in itemscontrol I can see the commands/methods beeing fired!
View:
<ListBox x:Name="BasketListBox" ItemsSource="{Binding BasketCollection}" MinWidth="200">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox commands:Checked.Command="{Binding CheckCommand}" IsChecked="False" </CheckBox>
</DataTemplate>
</ListBox.ItemTemplate>
Can anyone point me in the right direction please?
Cheers, Mcad.
EDIT 1:
The commanding now works, see solution below. BUT, I now run into another problem:
"An exception occurred while creating a region with name 'basketRegion'. The exception was: System.InvalidOperationException: ItemsControl's ItemsSource property is not empty. This control is being associated with a region, but the control is already bound to something else. If you did not explicitly set the control's ItemSource property, this exception may be caused by a change in the value of the inherited RegionManager attached property"
Created seperate question for this problem to make it more clean:
PRISM-MVVM, ItemsControl problem with View injection
You want every CheckBox to fire the same command? You could:
<CheckBox commands:Checked.Command="{Binding DataContext.CheckCommand, ElementName=BasketListBox}"
Or you could have every child view model expose the command via their own property.
Thanx Kent. You put me on the right path to solve this, ended up doing this:
<ListBox x:Name="basketListBox" ItemsSource="{Binding basketcollection}" MinWidth="200">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox commands:Checked1.Command="{Binding DataContext.CheckCommand, ElementName=basketListBox}" Content="{Binding basketName}"> </CheckBox>
</DataTemplate>
</ListBox.ItemTemplate>

How to do simple Binding in Silverlight?

I understand that Silverlight 3.0 has binding but just want a simple example on how to use this to read a property from a class.
I have a class called Appointment which as a String property called Location:
Public Property Location() As String
Get
Return _Location
End Get
Set(ByVal Value As String)
_Location = Value
End Set
End Property
With a Private Declaration for the _Location as String of course.
I want a XAML element to bind to this property to display this in a TextElement, but it must be in XAML and not code, for example I want something like this:
<TextBlock Text="{Binding Appointment.Location}"/>
What do I need to do to get this to work?
It has to be a Silverlight 3.0 solution as some WPF features are not present such as DynamicResource which is what I'm used to using.
Just to add that my XAML is being loaded in from a seperate XAML File, this may be a factor in why the binding examples don't seem to work, as there are different XAML files the same Appointment.Location data needs to be applied.
You have two options.
If the "Appointment" class can be used as the DataContext for the control or Window, you can do:
<TextBlock Text="{Binding Location}" />
If, however, "Appointment" is a property of your current DataContext, you need a more complex path for the binding:
<TextBlock Text="{Binding Path=Appointment.Location}" />
Full details are documented in MSDN under the Binding Declarations page. If neither of these are working, make sure you have the DataContext set correctly.
You need something in code, unless you want to declare an instance of Appointment in a resource and bind to that but I doubt thats what you want.
You need to bind the Text property to the Property Path "Location" then assign the DataContext of the containing XAML to an instance of the Appointment:-
<Grid x:Name="LayoutRoot" Background="White">
<TextBlock Text="{Binding Location}" />
</Grid>
Then in the control's load event:-
void Page_Loaded(object sender, RoutedEventArgs e)
{
this.DataContext = new Appointment() { Location = "SomePlace" };
}
Note in this case I'm using the default Page control.
If I'm reading correctly, you need to create an instance of Appointment, set the DataContext of the control to that instance and modify your binding to just say: Text="{Binding Location}"
Also, consider implementing INotifyPropertyChanged on your Appointment class to allow the data classes to notify the UI of property value changes.

Resources