I have a map with a MapItemsControl in my WP7 app that contains pushpins bound to items in a collection of custom classes. The pushpins are bound to properties of the item in the collection via a DataTemplate.
When an item is added to or removed from the collection, all pins display correctly, with properties as per bindings, but when just an items’ properties are modified, the UI does not update. The bindings just seem to get values from the source item upon loading, but I'd like them to keep the UI elements updated when the source collection items' properties are updated.
To illustrate, I’ll create a similar example:
Here’s a custom class:
Public Class Box
Property CurrentColor As Color
Property Location As GeoCoordinate
End Class
There's a collection of them:
Dim TempBoxes As ObservableCollection(Of Box)
My map control has a MapItemsControl in it:
<maps:MapItemsControl Name="BoxControl"
ItemTemplate="{StaticResource BoxTemplate}"
ItemsSource="{Binding TempBoxes}"/>
The item template is something like this:
<DataTemplate x:Key="BoxTemplate">
<maps:Pushpin Location="{Binding Location}" ManipulationStarted="BoxTouched">
<maps:Pushpin.Template>
<ControlTemplate>
<Ellipse Width="35" Height="35" Margin="54,148,366,584"
Stretch="Fill" StrokeThickness="4" Stroke="Black"
Fill="{Binding CurrentColor}" />
</ControlTemplate>
</maps:Pushpin.Template>
</maps:Pushpin>
</DataTemplate>
The touch event handler switches the pin's color between blue and red:
Private Sub BoxTouched(ByVal sender As Object, ByVal e As RoutedEventArgs)
With DirectCast(DirectCast(sender, Pushpin).DataContext, Box)
If .CurrentColor = Colors.Red Then
.CurrentColor = Colors.Blue
Else
.CurrentColor = Colors.Red
End If
End With
End Sub
Whenever I add or remove items from TempBoxes, the pins all render as they should (e.g. if I specify a color in the collection item, the pin shows the color).
Tapping on the item triggers the BoxTouched sub, which causes the item's color to change in the collection, but the UI doesn't change (pin color stays the same).
To get the UI to update the color, I have to get it to render the pins again, by adding something like this to BoxTouched:
BoxControl.ItemsSource = Nothing
BoxControl.ItemsSource = TempBoxes
I'm assuming there's a better way to do this?
In order for the DataTemplate to respond to changes in property values for your data object you need to implement the INotifyPropertyChanged interface on your data object so that property change notification is raised when your properties change.
Take a look at the VB samples in the MSDN documentation if you're unsure how to do this.
Related
I'm using a content dialog do display instance data when an item in a grid is selected.
In the calling page's view model, when an item is selected the following method is executed.
public virtual void ItemSelected(object sender, object parameter)
{
var arg = parameter as Windows.UI.Xaml.Controls.ItemClickEventArgs;
var clickedItem = arg.ClickedItem;
var item = clickedItem as ItemsModel;
var dialog = new ItemsDialog();
dialog.DataContext = item;
dialog.ShowAsync();
}
This shows the dialog, and the content is displayed as expected. Now I'm trying to split my xaml into different templates and I'm trying to use a ContentControl to display the appropriate template. I've written a DataTemplateSelector to help choose the correct template, but now I cannot figure out the data binding for the ContentControl (see simplified version below).
<ContentDialog.Resources>
<UI:MyTemplateSelector x:Key="MyTemplateSelector"
Template1="{StaticResource Template1}"
Template2="{StaticResource Template2}"/>
<DataTemplate x:Key="Template1"/>
<DataTemplate x:Key="Template2"/>
</ContentDialog.Resources>
<StackPanel>
<ContentControl DataContext="{Binding}"
ContentTemplateSelector="{StaticResource MyTemplateSelector}"/>
</StackPanel>
When debugging into my ContentTemplateSelector, my binding is always null. I've tried various forms of the binding syntax with no luck. How do I properly set the DataContext of the ContentControl to that of the ContentDialog?
When debugging into my ContentTemplateSelector, my binding is always
null
You need to set data binding for the Content property of ContentControl control, see Remarks in MSDN:
The Content property of a ContentControl can be any type of object,
such as a string, a UIElement, or a DateTime. By default, when the
Content property is set to a UIElement, the UIElement is displayed in
the ContentControl. When Content is set to another type of object, a
string representation of the object is displayed in the
ContentControl.
So the following xaml should work:
<StackPanel>
<ContentControl Content="{Binding}"
ContentTemplateSelector="{StaticResource MyTemplateSelector}"/>
</StackPanel>
Check my completed sample in Github
You have to bind Content also.
Content="{Binding}"
You have the data source (DataContext) and how the data is displayed (templates) and now you need to specify which of the properties brings that together.
I have a ListView whose Data Context is an ObservableCollection. I then used XAML to format the items and bind to their properties in the ListView in ListView.ItemTemplate.DataTemplate . Now, I want to add a TextBlock here to display the position of the item in the ListView through Binding in XAML. How to do that?
If I got your question right, you just need to add a TextBlock in each item DataTemplate with the index number inside the ListView, right?
If so, you can easily do that by doing something like this:
<DataTemplate x:Key="ItemTemplate">
<Grid>
<!--All your various UI elements here...-->
<!--Add a TextBlock with the formatting you want-->
<TextBlock Text="{Binding Position}"/>
</Grid>
</DataTemplate>
And in C#, add something like this after you've created your Source ObservableCollection, before assigning it to the Property inside your ViewModel:
for (int i = 0; i < myCollection.Count; i++)
{
myCollection[i].Position = Convert.ToString(i);
}
//And then, the usual
Source = myCollection
NB: I'm assuming you have a Property called Source in your ViewModel with INotifyPropertyChanged implemented. You will also have to modify the class you use inside your ObservableCollection and add the Position property of course :)
In Prism , MVVM, Windows 8.1 StoreApp I want the ViewModel to capture the SelectItem in a ListView. The ListView contains an ObservableCollection of objects. The ViewModel needs to lookup more details of the selected object and notify the View. The View in turn should show the details of the object.
I have implemented this, but the View allways shows the former object (after selecting a new one)
Of course what I'm looking for is an immediate and correct reaction in the View on selecting an object. Here are my codesnippets, all in VB code.
EDIT: I have put up another - smaller- example, using this approach. I made a recording of the process in this video. Please take a look at it before you read further!!
The objects come from the ViewModel as:
Public Property Persons As New ObservableCollection(Of Person)
They are bound to a usercontrol:
<Grid>
<ListView
ItemsSource="{Binding Persons}"
ItemTemplate="{StaticResource BusinessCard}">
<Interactivity:Interaction.Behaviors>
<Core:EventTriggerBehavior EventName="SelectionChanged">
<Behaviors:ListViewSelectionChangedAction/>
</Core:EventTriggerBehavior>
</Interactivity:Interaction.Behaviors>
</ListView>
</Grid>
Via the Behavior eventually this ends up in the ViewModel through this code:
Sub New(InjectedEventAggregator As IEventAggregator)
LocalEventAggregator = InjectedEventAggregator
LocalEventAggregator.GetEvent(Of PersonIsSelectedEvent)().Subscribe(AddressOf HandlePersonIsSelected, True)
This event is handled by this routine
Public Sub HandlePersonIsSelected(ByVal SelectedPerson As Person)
ActualPerson = SelectedPerson
End Sub
The last part of all this is the property that contains the ActualPerson like so:
Private Property _ActualPerson As Person
Public Property ActualPerson As Person
Get
Return _ActualPerson
End Get
Set(value As Person)
SetProperty(_ActualPerson, value)
End Set
End Property
EDIT: and this is the XAML that should show the selected ActualPerson:
<StackPanel DataContext="{Binding ActualPerson}" >
<Image Source="{Binding Photo}" Stretch="Fill" />
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding FirstName}" />
<TextBlock Text="{Binding FamilyName}" />
<TextBlock Text="{Binding Gender}" />
</StackPanel>
</StackPanel>
When I step through the code, I can see that the SelectedItem event is caught in the ViewModel, the handler for the selected person is called, the property is updated. Using Prism this would also mean, that the NotifyPropertyChanged event is fired. It IS fired indeed, otherwise the former object would not show either I guess.
But why is the View not updated immediatelty with the right (Person) object?
If you have a clue.... be my honored guest!
Regards
Let me try to understand what you said in "no reaction on the View". Are you saying the UI is NOT CHANGED, even though the 'ActualPerson = SelectedPerson' is called?
The controls have a binding property called MODE which decides the data flow. The MODE for the TextBlocks that display the Person information can be OneWay. It is possible that the binding is OneTime which is causing the issue.
Can you please confirm what is coming from your Repository? I'm assuming it isn't an observable collection. At least it shouldn't be, I'd think it would be a serialisable POCO object. This means that you will need to add the items to the observable collection. The way I do this if not using a CollectionView is to have a readonly variable of type ObservableCollection, which is never changed. Then when you make the request to messages, I would ensure the collection is cleared, ready for new the new items. Once the messages are returned, loop through each model item (message) within the response and convert them to MessageViewModels (a new class that contains bindable properties and validation (data annotations) as required. As each ViewModel item is created it is added to the observablecollection. The act of adding items to the collection will raise an event the listview is listening for and therefore will display the item (as long as the MessageViewModel has an associated data template.
private readonly _messages = new ObservableCollection<MessageViewModel>();
Public ObservableCollection<MessageViewModel> Messages {get { return _messages;}}
OnNavigateTo
Messages.Clear;
foreach(var message in await _messageRepository,GetMessagesAsync())
{
Messages.Add(new MessageViewModel(){Name = message.Name, Text = message.Text});
}
Does that make sense?
One bad solution, but maybe it is helpful. You can call "again" OnProperyChanged. I say "again" because it is supposed that SetProperty calls it, but I am having some issues with VB.NET and SetProperty too.
Private Property _ActualPerson As Person
Public Property ActualPerson As Person
Get
Return _ActualPerson
End Get
Set(value As Person)
SetProperty(_ActualPerson, value)
OnPropertyChanged("ActualPerson")
End Set
End Property
Edited 04/02/15: the good solution is here: OnPropertyChanged not fired when SetProperty is called
As simple as removing the word "Property" in the private declaration. It is because it is passed ByRef, so it cannot be a property.
I have a WinRT/C#/XAML app with a view that has a vertical ListView of items. Depending on the amount of items the ListView shows a vertical scrollbar. Here's the XAML definition:
<UserControl.Resources>
<CollectionViewSource
x:Name="myViewSource"
Source="{Binding myViewModel.Items}" />
</UserControl.Resources>
...
<ListView
x:Name="myListView"
ItemsSource="{Binding Source={StaticResource myViewSource}}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
</ListView>
Now everytime I navigate to this view, the selected item of the ListView is chosen by setting the databound SelectedItem property in the view model from code behind (OnNavigatedTo). My problem: the ListView doesn't scroll automatically to this selected item. The scrollbar remains at the top of the ListView and the user has to scroll manually to see the selected item.
I tried to execute myListView.ScrollIntoView(MyViewModel.SelectedItem); after setting the SelectedItem in the code behind (in OnNavigatedTo), but it doesn't work. The scrollbar remains at the top.
I'm aware of this thread on SO: Scroll WinRT ListView to particular group .
This seems to be a similar problem. But when I walk the visual tree of the ListView manually or with the WinRT XAML Toolkit, it doesn't find a ScrollViewer (returns null instead).
Thanks to Filip I noticed that calling ScrollIntoView() in OnNavigatedTo() was too early, because the ListView control is not loaded yet in this place.
The first solution idea was to bind the Loaded event of the ListView:
myListView.Loaded += (s, e) =>
myListView.ScrollIntoView(MyViewModel.SelectedItem);
Unfortunately that causes a nasty visual effect, where current ListView items overlap with the selected item for parts of a second, before everything is rearranged well.
The final solution I found is to call ScrollIntoView() asynchronously via the Dispatcher of the view:
myListView.Loaded += (s, e) => Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
() => myListView.ScrollIntoView(MyViewModel.SelectedItem));
With this solution the layouting works fine.
I had a similar need and resolved it in a slightly different manner. I subscribed to the SelectionChangedEvent from the ListView and performed the scrolling within the handler.
XAML:
<ListView x:Name="myListView" SelectionChanged="myListView_SelectionChanged" ...>
</ListView>
Code:
private void myListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
myListView.ScrollIntoView(myListView.SelectedItem);
}
I have a silverlight page with a textblock and button on it. Like this:
<TextBlock x:Name="txbNote" Margin="50,50" Text="Hello"/>
<Button x:Name="btnCheck" Height="40" Click="btnCheck_Click" ClickMode="Press" Margin="50,50,50,50" Content="Check Service"/>
Here is the handler for the click event:
Private Sub btnCheck_Click(ByVal sender As Object, ByVal e As EventArgs) 'Handles btnCheck.Click
txbNote.Text = "I Was Clicked"
End Sub
It works... but...
Why doesn't this work?
<Button x:Name="btnCheck" Height="40" Click="btnCheck_Click" ClickMode="Press" Margin="50,50,50,50" Content="Check Service"/>
<TextBlock x:Name="txbNote" Margin="50,50" Text="Hello"/>
The only change is the relative position of the textblock and button. The button's click event (and every other event I tried) just doesn't fire unless the textblock is before the button in the xaml.
You may need to post more code as this could be an issue with the surrounding tags, such as the container that these controls are in.
If you're unable to paste it all to StackOverflow, use www.dpaste.com or www.pastebin.com.
If you place these elements in the Panel instead of the Grid, it starts working.
As you mentioned grid, if you placed two items in grid, the last item is on the top in hierarchy, all top level events are received by TextBlock, you should create two columns in grid and put items in individual columns.