AppBarButton is not binding to MVVM Relay command in ListView - mvvm-light

Hi I am trying to add AppBarButton in a ListView and binded command to RelayCommand in ViewModel here is my xaml code
<DataTemplate x:Key="MyTemplage" >
<Grid HorizontalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="auto"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="4*" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<AppBarButton Grid.Column="1" Command="{Binding DoCommand,Mode=OneWay}">
<AppBarButton.Icon>
<BitmapIcon UriSource="ms-appx:///assets/doIcon.png"></BitmapIcon>
</AppBarButton.Icon>
</AppBarButton>
</DataTemplate>
<ListView HorizontalAlignment="Left" Margin="0,45,0,0" Background="Khaki" VerticalAlignment="Top" ItemsSource="{Binding AList, Mode=TwoWay}"
ItemTemplate="{StaticResource MyTemplage}">
</ListView>
here is my Relay command code in VM
private RelayCommand _DoCommand;
/// <summary>
/// Gets.
/// </summary>
public RelayCommand DoCommand
{
get
{
return _DoCommand
?? (_DoCommand = new RelayCommand(
() =>
{
DoSomething();
}));
}
}
DoCommand is not raising in ViewModel. If I register click event handler in code behind it works fine. also AppBarButton is wokring fine with MVVM if I use it in Page.Bottombar.
any ideas?

The problem is that the binding inside the ListView DataTemplate is not to the ViewModel object, but to a different DataContext, in your case it's binding to a list called AList which is inside the ViewModel and contains a list of model classes - so the binding engine is essentially looking for DoCommand inside that model class.
In order to get the binding to work, you must make sure that the binding is pointing to the whole ViewModel DataContext, where the RelayCommand actually is. One way you could do that is to bind to some element in your page which has the DataContext set to the whole ViewModel:
Command="{Binding DataContext.DoCommand, ElementName=pageRoot, Mode=OneWay}"
In this case, I'm binding to pageRoot, where pageRoot is the name of the root page element of your page which has the proper DataContext set - ViewModel where the RelayCommand actually is.

Related

Copy attached properties to new instance in xamarin forms

I am creating new instance of grid object which have two labels as following.
<Grid x:Name="Grid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="70*"></ColumnDefinition>
<ColumnDefinition Width="30*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Label x:Name="Label1" Text="Label1" Grid.Column="0" Grid.Row="0"></Label>
<Label x:Name="Label2" Text="Label2" Grid.Column="1" Grid.Row="0"></Label>
</Grid>
I am using Activator.CreateInstance method as given below:
Grid ClonedView =(Grid) Activator.CreateInstance(Grid.GetType());
foreach (PropertyInfo propertyInfo in Grid.GetType().GetProperties())
{
if (propertyInfo.CanRead && propertyInfo.CanWrite )
{
object PropertyValue = propertyInfo.GetValue(Grid, null);
propertyInfo.SetValue(ClonedView, PropertyValue);
}
}
When I use this ClonedView in my page, it Cloned Grid don't understand that Label2 should appear in second column of grid. I did a lot of research on it but could not find any solution. Please let me know if anybody has any solutions.
thanks
They way you're thinking about this, feels very JavaScript like to me. You want to create a deep copy by copying all properties. However the Xamarin objects are way too complex for something like this (even if you where able to do a deep copy, Bindings for example won't work with simple copying).
So instead of making a single Grid object and trying to create copies of it, another way of looking at it would be "I want to define a template, and then create multiple instances according to that template". For this Xamarin has the DataTemplate class, which enables us to do something like this in xaml:
<ContentPage>
<ContentPage.Resources>
<ResourceDictionary>
<DataTemplate x:Key="MyGridTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="70*"></ColumnDefinition>
<ColumnDefinition Width="30*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<Label Text="Label1" Grid.Column="0" Grid.Row="0"></Label>
<Label Text="Label2" Grid.Column="1" Grid.Row="0"></Label>
</Grid>
</DataTemplate>
</ResourceDictionary>
</ContentPage.Resources>
<Button Text="Add Grid" Clicked="AddGridClicked">
</Button>
<StackLayout x:Name="GridHolder">
<StackLayout>
<ContentPage>
And in our code behind we can implement AddGridClicked like this:
private void AddGridClicked(object sender, EventArgs e)
{
var myGridTemplate = (DataTemplate) Resources["MyGridTemplate"];
GridHolder.Children.Add((View)myGridTemplate.CreateContent());
}
Now the downside is that you need to define a DataTemplate instead of just pointing at an object and saying getting a copy. Also keep in mind that x:Name of a View in the DataTemplate can't be accessed in code behind of your page, but with a good Xamarin setup that should not be needed. The upside is that this works, complete with possible bindings, so you're allowed to define a Label in your grid like this:
<Label Text="{Binding Name}" Grid.Column="0" Grid.Row="0"></Label>
And then you can give each instance if your templated Grid a different BindingContext to bind different data to it's views.
Hope this helps

XamarinForm DataTemplate ListView Binding

I want to bind to a variable from inside a CustomControl referenced in a ListView DataTemplate
This is the situation:
I have a custom control containing a ListView with many DataTemplates to display the various ViewCell (it's basically a set of dynamic fields that must be displayed according to their "type").
<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:s4gvForms="clr-namespace:S4GVMobile.Forms;assembly=S4GVMobile"
xmlns:s4gvControls="clr-namespace:S4GVMobile.Controls;assembly=S4GVMobile"
x:Class="S4GVMobile.Controls.AttributesList"
x:Name="S4GVAttributesListControl"
>
<ContentView.Resources>
<ResourceDictionary>
<!-- MANY DATA TEMPLATES HERE, ONLY ONE LEFT FOR REFERENCE -->
<DataTemplate x:Key="s4gvAttributePictureTemplate">
<ViewCell x:Name="s4gvAttributePictureViewCell">
<ViewCell.View>
<StackLayout Orientation="Vertical">
<Label Text="{Binding Attribute.Key}" />
<s4gvControls:AttributePicture ParentID="{Binding Source={x:Reference s4gvAttributePictureViewCell}, Path=Parent.BindingContext.EntityID}" AttributeValue="{Binding .}" />
</StackLayout>
</ViewCell.View>
</ViewCell>
</DataTemplate>
<!-- ... -->
<!-- AGAIN, LONG LIST IN THE SELECTOR -->
<s4gvForms:AttributeDataTemplateSelector x:Key="s4gvAttributeDataTemplateSelector"
AttributePictureTemplate="{StaticResource s4gvAttributePictureTemplate}"
/>
</ResourceDictionary>
</ContentView.Resources>
<StackLayout>
<ListView x:Name="s4gvAttributes" HasUnevenRows="True" ItemTemplate="{StaticResource s4gvAttributeDataTemplateSelector}" ItemsSource="{Binding AttributeValues}" />
</StackLayout>
I'm passing to this custom control both the list of items (required by the ListView) and an additional EntityID.
<s4gv:AttributesList x:Name="s4gvSerialAttributes" AttributeValues="{Binding Serial.AttributeValues}" EntityID="{Binding Serial.SerialID}" VerticalOptions="FillAndExpand"/>
The custom control has the required bindable properties (AttibuteValues and EntityID) and relies on a dedicated ViewModel
I need to retrieve the EntityID from inside the DataTemplate (and put it in the ParentID property); this value is in the custom control's ViewModel but it is "outside" the ListView's DataTemplates. I've tried using the Parent.BindingContext (it works on Command and CommandParameter) but it seems that it can only go up to the ListView's context and not higher though I need to move one further step up to the CustomControl itself.
<s4gvControls:AttributePicture ParentID="{Binding Source={x:Reference s4gvAttributePictureViewCell}, Path=Parent.BindingContext.EntityID}" AttributeValue="{Binding .}" />
Any idea? I'm happy to restructure the whole thing if required.

Xamarin : Change label text inside listview

I am new with xamarin, I am facing an issue in my xamarin forms project.
I have a Label and 2 images inside listview-viewcell. I want to change the label text onclick of images. Increase the label text value by 1 if click like and decrease by 1 if click unlike. I use the label x:name, but it is not accessible in class. How can i fix this?
<StackLayout>
<ListView>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label
Text="{Binding likeCount}"
x:Name="likecount"/>
<Image Source="like.png">
<Image.GestureRecognizers>
<TapGestureRecognizer
Tapped="LikeOrUnlikeTweet"
CommandParameter="{Binding .}"
NumberOfTapsRequired="1" />
</Image.GestureRecognizers>
</Image
<Image Source="unlike.png">
<Image.GestureRecognizers>
<TapGestureRecognizer
Tapped="LikeOrUnlikeTweet"
CommandParameter="{Binding .}"
NumberOfTapsRequired="1" />
</Image.GestureRecognizers>
</Image
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
Anybody please suggest a solution with working code.
Thanks in advance
First of you need to create command in your VM and need to change as follow:
<TapGestureRecognizer
Tapped="{Binding Source={x:Reference YourListName}, Path=BindingContext.LikeOrUnlikeTweetCommand}"
CommandParameter="{Binding .}"
NumberOfTapsRequired="1" />
ViewModel Code
public ICommand LikeOrUnlikeTweetCommand { get; }
Initialise in constructor
LikeOrUnlikeTweetCommand = new Command<YourModelDto>(LikeOrUnlikeTweet);
Function code in this you change your count value and update list.
void LikeOrUnlikeTweet(YourModelDto yourModelDto)
{
....
}
I would take one of 2 paths for doing this:
Use an ObservableCollection as ItemsSource for your list, obvious when you change properties of an item they would be reflected in the list. What the list view would do is it would replace the old item with the new one modified with your new values. You can make a search about what an ObservableCollection collection is.
My actual way, as im fighting low performance of android with listviews, and this makes it: you can create a custom class for ViewCell, avoid low-speed bindings, set all properties inside your custom ViewCell by code. When the cell bindigcontext changes more likely the ItemsSource item is being assigned to your cell, so you can now take its value ans set it up.
protected override void OnBindingContextChanged()
{
SetupCell(); //assign values to named controls manually
base.OnBindingContextChanged();
}
public void SetupCell()
{
var item = BindingContext as YouItemClass;
if (item == null) return;
txtTitle.Text = item.Name;
//etc
...

TreeView ContextMenu not fired events

TreeView ContextMenu not fired events (ContextMenu Opened event not fire), I'm trying Caliburn.Micro.Telerik convention not solved problem.
TreeViewItemTemplate
<HierarchicalDataTemplate x:Key="TreeViewItemTemplate" ItemsSource="{Binding Types}"
ItemTemplateSelector="{StaticResource NamespaceItemTemplateSelector}">
<StackPanel Orientation="Horizontal">
<Path
Data=""
Height="11.458" Margin="0,0,5,0" RenderTransformOrigin="0.5,0.5" Stretch="Fill"
UseLayoutRounding="False" Width="11.264">
<Path.Fill>
<SolidColorBrush Color="#FF333333">
<SolidColorBrush.RelativeTransform>
<MatrixTransform Matrix="Identity" />
</SolidColorBrush.RelativeTransform>
<SolidColorBrush.Transform>
<MatrixTransform Matrix="Identity" />
</SolidColorBrush.Transform>
</SolidColorBrush>
</Path.Fill>
<Path.RenderTransform>
<TransformGroup>
<ScaleTransform />
<SkewTransform />
<RotateTransform />
<TranslateTransform />
</TransformGroup>
</Path.RenderTransform>
</Path>
<TextBlock Text="{Binding Header}" Tag="{Binding DataContext, RelativeSource={RelativeSource Self}}">
<TextBlock.ContextMenu>
<ContextMenu cal:Action.TargetWithoutContext="{Binding RelativeSource={RelativeSource Self},Path=UIElement.Tag}" cal:Message.Attach="[Event Opened] = [Action Opened($dataContext)]" >
<MenuItem Header="NewChild" cal:Message.Attach="NewChild($datacontext)"/>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</StackPanel>
TreeView
<TreeView x:Name="TreeView" ItemTemplate="{StaticResource TreeViewItemTemplate}" ItemsSource="{Binding Source}"/>
Before i tell you what was the problem i want to point out that the version of the code you uploaded is a bit different than the one you posted in your question.
OK, The problem lies exactly in this section of the template:
<TextBlock Text="{Binding Header}" Tag="{Binding DataContext, RelativeSource={RelativeSource Self}}">
<TextBlock.ContextMenu>
<ContextMenu cal:Action.TargetWithoutContext="{Binding RelativeSource={RelativeSource Self},Path=UIElement.Tag}" cal:Message.Attach="[Event Opened] = [Action Opened($dataContext)]" >
<MenuItem Header="NewChild" cal:Message.Attach="NewChild($datacontext)"/>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
To fix it you need to replace it with this modified version:
<TextBlock Text="{Binding Header}" Tag="{Binding DataContext, RelativeSource={RelativeSource Self}}">
<TextBlock.ContextMenu>
<ContextMenu cal:Action.TargetWithoutContext="{Binding RelativeSource={RelativeSource Self},Path=PlacementTarget.Tag}" cal:Message.Attach="[Event Opened] = [Action ContextMenuOpened($source)]">
<MenuItem Header="New" cal:Message.Attach="[Event Click] = [Action ClickMenuItem($source)]"/>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
The problem was caused by three issues:
In the template and specifically in this line of code <ContextMenu cal:Action.TargetWithoutContext="{Binding RelativeSource={RelativeSource Self},Path=UIElement.Tag}" the Path=UIElement.Tag was causing a binding error because there is no property on type ContextMenu that is called UIElement. Instead you have to replace that with the PlacementTarget property which represents the element in the user interface on which the context menu was opened.
The second issue is that you were putting the methods that handle those events on the wrong class. They shouldn't be on the ShellViewModel because in the template you are setting the Action.Target to be the DataContext behind the TextBlock which is actually the NamespaceViewModel in your situation, so the methods should go on the NamespaceViewModel.
The third and final issue is that your methods had the wrong signature so they couldn't be found by Caliburn.Micro. What i mean is that you are declaring your ContextMenuOpened method like this: void ContextMenuOpened(sender args, RoutedEventArgs args); but in the template you are calling the method like this: [Action ContextMenuOpened($source)] which sends the method the FrameworkElement that caused the event (ContextMenu in this case), so you should change the method signature for this event to this: void ContextMenuOpened(ContextMenu source) or the more general void ContextMenuOpened(FrameworkElement source).
Summary:
Fix your template to use the PlacementTarget property.
Move your methods to NamespaceViewModel.
Fix your methods signatures.
Finally: If you want to know everything about actions and action messages and what parameters get passed to what then you should really read the documentation wiki about Actions.

Panorama Binding on WP7, Items collection must be empty before using ItemsSource

i keep getting this error: Items collection must be empty before using ItemsSource, when i attempt to bind data to a Panorama control. below is my xaml snippet.
<controls:Panorama x:Name="panorama">
<controls:PanoramaItem >
<StackPanel>
<TextBlock Text="{Binding Text}"/>
</StackPanel>
</controls:PanoramaItem>
</controls:Panorama>
in my code behind (xaml.cs), i do something like this:
protected override void OnNavigatedTo(NavigationEventArgs e) {
string id = NavigationContext.QueryString["id"];
ObservableCollection<MyObject> list = DataAccessService.get(id);
panorama.ItemsSource = list;
base.OnNavigatedTo(e);
}
note that MyObject has a Text property. any help is appreciated.
after modifying it per the link below, the same exception is still thrown.
<controls:Panorama x:Name="panorama">
<controls:Panorama.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}"/>
</DataTemplate>
</controls:Panorama.HeaderTemplate>
<controls:PanoramaItem >
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Text}"/>
</StackPanel>
</DataTemplate>
</controls:PanoramaItem>
</controls:Panorama>
finally, after continuing further with the user below's help, this is the solution that got rid of the exception.
<controls:Panorama x:Name="panorama">
<controls:Panorama.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}"/>
</DataTemplate>
</controls:Panorama.HeaderTemplate>
<controls:Panorama.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Text}"/>
</StackPanel>
</DataTemplate>
</controls:Panorama.ItemTemplate>
</controls:Panorama>
Your problem is that you're building the Panorama in XAML as though it were static rather than preparing it to be data-bound.
Take a look at this quick tutorial for a Data Binding Panorama Control:
Data Binding Panorama Control WP7 MVVM
Notice the difference in how your XAML is being constructed for the control. Instead of setting the Items collection on the Panorama control, you need to set the HeaderTemplate and ItemTemplate so that the control knows how to render things once data is bound to it.

Resources