Relay Command doesn't execute - data-binding

I've decided to use the mvvm architecture in my project and tried converting my code to it.
But it looks like I'm misunderstanding something, as the properties I'd like to set in an relay command are either not changed or the changing has no effect. Either way this behavior is not desired.
My code is very simple and looks like this:
MainViewModel.cs
public partial class MainViewModel : ObservableRecipient
{
[ObservableProperty] private string testText;
[RelayCommand]
private void Toggle()
{
testText = "pressed";
}
}
Mainview.xaml
<Page
x:Class="Calendarium.Views.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:viewModels="using:Calendarium.ViewModels"
d:DataContext="{d:DesignInstance Type=viewModels:MainViewModel}">
<Grid>
<TextBlock Text="{Binding TestText}">
<Button Command="{Binding ToggleCommand}">
</Grid>
</Page>

You seem to have set the design time DataContext only.
You also need to set the actual DataContext that is used at runtime:
<Page
x:Class="Calendarium.Views.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:viewModels="using:Calendarium.ViewModels"
d:DataContext="{d:DesignInstance Type=viewModels:MainViewModel}">
<Page.DataContext>
<viewModels:MainViewModel />
</Page.DataContext>
<Grid>
...
</Grid>
</Page>
Besides, you should set the value of the generated property in your Toggle method:
[RelayCommand]
private void Toggle()
{
TestText = "pressed";
}

Related

Xamarin ViewCell subclass getting error in run time. "ViewCell Must Have a View"?

I am getting this runtime error saying that the CellView needs a View. I have not found anything on this so far, here on StackOverflow or on google, so I am in the total dark about what needs to be fixed.
I need to set up a list view with 2 sections. the first one is for the bikes that are in the database and the other section is for the ones that we detected but are not saved in the DB yet.
Both sections use the same UI (cell) and so I need to be able to have a sliding button (ContextAction MenuItem) in the first section and for the second section this action needs to be disabled/removed.
If it's not possible then I would need to have the action for the second section "Add" instead of "Forget".
This is why I created a subclass.
this is my code.
(edit: corections)
---- .xaml file ----
<?xml version="1.0" encoding="UTF-8"?>
<ViewCell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="EGrid18.Components.BikeSelectionCell"
x:Name="ThisCell">
<ViewCell.View>
<StackLayout>
<Grid ColumnSpacing="10"
BindingContext="{x:Reference ThisCell}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="32"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Label Text="{Binding BikeName}"
StyleClass="bikecellname"
Grid.Row="0"
Grid.Column="0"
VerticalOptions="Center"
HorizontalOptions="StartAndExpand"/>
<Image Source="{Binding Image}"
HeightRequest="16"
WidthRequest="16"
VerticalOptions="Center"
Aspect="AspectFit"
Grid.Column="1"
Grid.Row="0"/>
<Label Text="{Binding Distence, StringFormat='{0:F2} M'}"
Grid.Row="0"
Grid.Column="2"
VerticalOptions="Center"/>
</Grid>
</StackLayout>
</ViewCell.View>
</ViewCell>
---- .cs file ----
using System.Runtime.CompilerServices;
using System.Windows.Input;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace EGrid18.Components
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class BikeSelectionCell : ViewCell
{
public static BindableProperty BikeNameProperty =
BindableProperty.Create(nameof(BikeName), typeof(string), typeof(BikeSelectionCell), string.Empty);
public string BikeName
{
get => (string)GetValue(BikeNameProperty);
set => SetValue(BikeNameProperty, value);
}
public static BindableProperty ImageProperty =
BindableProperty.Create(nameof(Image), typeof(string), typeof(BikeSelectionCell), string.Empty);
public string Image
{
get => (string)GetValue(ImageProperty);
set => SetValue(ImageProperty, value);
}
public static BindableProperty DistenceProperty =
BindableProperty.Create(nameof(Distence), typeof(double), typeof(BikeSelectionCell), 0.0);
public double Distence
{
get => (double)GetValue(DistenceProperty);
set => SetValue(DistenceProperty, value);
}
public static readonly BindableProperty ForgetCommandProperty =
BindableProperty.Create(nameof(Command), typeof(ICommand), typeof(BikeSelectionCell), null);
public ICommand ForgetCommand
{
get { return (ICommand)GetValue(ForgetCommandProperty); }
set { SetValue(ForgetCommandProperty, value); }
}
public Command OnForget => new Command(() => Execute(ForgetCommand));
// Helper method for invoking commands safely
public static void Execute(ICommand command)
{
if (command == null) return;
if (command.CanExecute(null))
{
command.Execute(null);
}
}
protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
base.OnPropertyChanged(propertyName);
if(propertyName == nameof(Image))
{
this.ContextActions.Clear();
if (Image != "unknown")
{
this.ContextActions.Add(new MenuItem()
{
Text = "Forget",
Command = ForgetCommand,
CommandParameter = this
});
}
}
}
}
}
--- the .xaml file that consumes the CellView subclass ---
<?xml version="1.0" encoding="UTF-8" ?>
<ContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:vm="clr-namespace:EGrid18.ViewModels"
xmlns:iOS="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core"
xmlns:components="clr-namespace:EGrid18.Components"
iOS:Page.UseSafeArea="True"
x:Class="EGrid18.Views.BikeSelectionPage"
Title="Bike Selection List">
<ContentPage.Resources>
<ResourceDictionary>
<vm:BikeListViewModel x:Key="vm"/>
<StyleSheet Source="/CSS/Styles.css"/>
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.ToolbarItems>
<ToolbarItem x:Name="ScanButton" Text="Scan"
Command="{Binding Source={StaticResource vm}, Path=OnScanCommand}"
CommandParameter="{Binding Source={StaticResource vm}, Path=IsScanning}" />
</ContentPage.ToolbarItems>
<ContentPage.Content>
<StackLayout Margin="8,8,8,8">
<Label Text="Bike Selection Page"
StyleClass="titlelabel"/>
<ListView x:Name="ListView"
BindingContext="{StaticResource vm}"
StyleClass="listview"
ItemsSource="{Binding BikeList}"
IsGroupingEnabled="True"
SelectedItem="{Binding SelectedBike}"
Margin="8,8,8,8">
<ListView.GroupHeaderTemplate>
<DataTemplate>
<ViewCell>
<Label Text="{Binding Heading}"
VerticalOptions="Center"
Margin="8,8,8,8"/>
</ViewCell>
</DataTemplate>
</ListView.GroupHeaderTemplate>
<ListView.ItemTemplate>
<DataTemplate>
<components:BikeSelectionCell
BikeName="{Binding Name}"
Image="{Binding Image}"
Distence="{Binding Distence}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage.Content>
</ContentPage>
A ViewCell is not a View - it isn't part of View heirarchy. It is a "wrapper" that is needed by ListView, around each item.
Personally, I never create subclasses of ViewCell, though that can be done.
IMHO, Its more straightforward to make a custom View, then wrap that in <ViewCell> to use with ListView.
As a View, you can inherit from any view class. If you don't know what else to use, inherit from ContentView. (Or from StackLayout, if need to add multiple children views.) In your case, your View is a StackLayout.
public partial class BikeSelectionView : StackLayout
... // All the lines of code you have in BikeSelectionCell go here.
xaml:
<StackLayout ... x:Name="ThisCell">
<Grid ColumnSpacing="10" ...>
... All the remaining lines of code from your xaml ...
</StackLayout>
Usage inside ListView's ItemTemplate:
<DataTemplate>
<ViewCell>
<components:BikeSelectionView
BikeName="{Binding Name}"
Image="{Binding Image}"
Distence="{Binding Distence}"/>
</ViewCell>
</DataTemplate>
One advantage of this approach is if you switch from ListView to CollectionView (which does not use ViewCells), you can still use the custom class:
<CollectionView.ItemTemplate>
<DataTemplate>
<components:BikeSelectionView ... />
</DataTemplate>
</CollectionView.ItemTemplate>
The error was coming from a missing constructor that needed to initialize the Component.
public BikeSelectionCell()
{
InitializeComponent();
}
In the end I used the suggestion ToolmakerSteve. I also used a custom DataTemplateSelector to set specifics DataTemplate for the 2 different sections
I am setting this as the answer for any one looking for this problem as the problem was really the missing call to InitializeComponent();

How to make from xamarin.forms 4.3 identical to СarouselPage?

The documentation says that I should use the page carousel for the place, this is a carousel view. But I do not understand how to do this.
I have three completely different pages. If earlier I could do like this. look how easy it is.
<CarouselPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:local="clr-namespace:test.Views"
x:Class="test.Corusel">
<CarouselPage.Children>
<local:Page1></local:Page1>
<local:Page2></local:Page2>
</CarouselPage.Children>
Now, when I try to make a look identical to the CarouselPage, I just have a white screen.
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:local="clr-namespace:test.Views"
x:Class="test.Corusel">
<CarouselView>
<CarouselView.ItemTemplate>
<DataTemplate>
<StackLayout>
<local:View1></local:View1>
<local:View2></local:View2>
</StackLayout>
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
</ContentPage>
People tell me that there should be an itemSource. Without itemSource, content will not be displayed. But as I understand it, itemSource does not allow you to make different layouts for data.
I don’t understand how I can make a copy of СarouselPage from СarouselView (or CollectionView) if:
My pages have different layouts!
To load content into different layouts, I use different functions!
In my knowledge what you are looking for is the DataTemplateSelector
Create a subclass of Selector with properties that hold the templates:
public class PersonDataTemplateSelector : DataTemplateSelector
{
public DataTemplate ValidTemplate { get; set; }
public DataTemplate InvalidTemplate { get; set; }
protected override DataTemplate OnSelectTemplate (object item, BindableObject container)
{
return ((Person)item).DateOfBirth.Year >= 1980 ? ValidTemplate : InvalidTemplate;
}
}
Add them to your resources:
<ContentPage.Resources>
<ResourceDictionary>
<DataTemplate x:Key="validPersonTemplate">
<ViewCell>
...
</ViewCell>
</DataTemplate>
<DataTemplate x:Key="invalidPersonTemplate">
<ViewCell>
...
</ViewCell>
</DataTemplate>
<local:PersonDataTemplateSelector x:Key="personDataTemplateSelector"
ValidTemplate="{StaticResource validPersonTemplate}"
InvalidTemplate="{StaticResource invalidPersonTemplate}" />
</ResourceDictionary>
</ContentPage.Resources>
Use them:
ItemTemplate="{StaticResource personDataTemplateSelector}"
For more information check the microsoft documents: https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/templates/data-templates/selector

Xamarin.Forms: How to hide the Navigation Bar Separator on iOS devices?

I have changed the default color of the navigation bar by using these codes in app.xaml as I am unable to make it transparent on iOS devices:
<Style TargetType="NavigationPage">
<Setter Property="BarBackgroundColor" Value="#0a82b8" />
<Setter Property="BarTextColor" Value="#ffffff" />
</Style>
Now, there is an unnecessary navigation bar separator on iOS:
On Microsoft's official website, it says these codes can be helpful:
This platform-specific hides the separator line and shadow that is at
the bottom of the navigation bar on a NavigationPage. It's consumed in
XAML by setting the NavigationPage.HideNavigationBarSeparator bindable
property to false:
<NavigationPage ...
xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core"
ios:NavigationPage.HideNavigationBarSeparator="true">
</NavigationPage>
Alternatively, it can be consumed from C# using the fluent API:
using Xamarin.Forms.PlatformConfiguration;
using Xamarin.Forms.PlatformConfiguration.iOSSpecific;
public class iOSTitleViewNavigationPageCS : Xamarin.Forms.NavigationPage
{
public iOSTitleViewNavigationPageCS()
{
On<iOS>().SetHideNavigationBarSeparator(true);
}
}
Source: Hiding the Navigation Bar Separator on a NavigationPage
The problem is that when I want to paste the ios:NavigationPage.HideNavigationBarSeparator="true" property into the <NavigationPage>tag, it gives this error:
No property, bindable property, or event found for 'HideNavigationBarSeparator', or mismatching type between value and property.
My full codes are:
<?xml version="1.0" encoding="utf-8"?>
<MasterDetailPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:My.App" x:Class="My.App.MainPage">
<MasterDetailPage.Master>
<ContentPage Title="Menu" BackgroundColor="#0a82b8" Icon="menu.png">
<StackLayout Orientation="Vertical">
<ListView x:Name="navigationDrawerList" RowHeight="55" SeparatorVisibility="None" BackgroundColor="#ffffff" ItemSelected="OnMenuItemSelected">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<!-- Main design for our menu items -->
<StackLayout VerticalOptions="FillAndExpand" Orientation="Horizontal" Padding="20,10,0,10" Spacing="20">
<Label Text="{Binding Title}" FontSize="Large" VerticalOptions="Start" HorizontalOptions="CenterAndExpand" TextColor="#28DDFF" FontAttributes="Bold" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
</MasterDetailPage.Master>
<MasterDetailPage.Detail>
<NavigationPage xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core"
ios:NavigationPage.HideNavigationBarSeparator="true">
</NavigationPage>
</MasterDetailPage.Detail>
</MasterDetailPage>
Here is the tutorial which I followed for the MasterDetail navigation. The file which has <NavigationPage> tag is named as MainPage.xaml
You can use a custom renderer to hide that shadow:
[assembly: ExportRenderer(typeof(NavigationPage), typeof(CustomNavigationPage))]
namespace CustomNavigationPage.iOS
{
public class CustomNavigationPage : NavigationRenderer
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
NavigationBar.SetBackgroundImage(new UIKit.UIImage(), UIKit.UIBarMetrics.Default);
NavigationBar.ShadowImage = new UIKit.UIImage();
}
}
}
While you have accepted the answer, this one is more precise - you haven't followed the instructions that you have pasted from the site as you have ommited this line:
xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core"
Place UINavigationBar.Appearance.ShadowImage = new UIImage(); inside the public override bool FinishedLaunching(UIApplication app, NSDictionary options) method within AppDelegate.cs file. AppDelegate.cs resides within the iOS project of Xamarin.Forms

Xamarin.Forms: Can I embed one ContentPage or ContentView into another ContentPage

I have multiple ContentPage xaml files in my Xamarin project. I want to embed a shared piece of xaml into each ContentPage. There is nothing particularly special about the shared piece of xaml (it does't need to do anything platform specific). Shouldn't it be just as easy as embedding a tag in the xaml of the ContentPage to include the shared xaml file? Can someone point me in the right direction?
Thank you very much IdoT, It did work for me, but after adding some lines.
As this will help in making templates/custom controls/sub forms, easily added/shared across Xamarin.Forms.
Here is my full work based on your suggestion, so it can be used as is with others:
HeaderNavigationBar.xaml
<?xml version="1.0" encoding="utf-8" ?>
<StackLayout xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="App9.MVC.Views.HeaderNavigationBar"
Orientation="Horizontal"
HorizontalOptions="FillAndExpand"
Padding="10"
ackgroundColor="White">
<Button Text="Internal 1" />
<Button Text="Internal 2" />
</StackLayout>
As you can see, added:
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
and in the HeaderNavigationBar.cs, it was declared as StackLayout:
HeaderNavigationBar.cs
using Xamarin.Forms;
namespace App9.MVC.Views
{
public partial class HeaderNavigationBar : StackLayout
{
public HeaderNavigationBar()
{
InitializeComponent();
}
}
}
then for the page that will hold it/show it:
MainView.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:common="clr-namespace:App9.MVC.Views;assembly=App9"
x:Class="App9.MVC.Views.MainView">
<StackLayout Padding="0,0,0,20">
<common:HeaderNavigationBar>
<Button Text="External 1" />
</common:HeaderNavigationBar>
<Button Text="Test Button 1
x:Name="btnPage1"
Clicked="btnPage1_clicked" />
</StackLayout>
</ContentPage>
As you can notice, the namespace has the full path, in the MainView:
xmlns:common="clr-namespace:App9.MVC.Views;assembly=App9"
Also, you can notice that there is a button called External 1, this will also show with the
Internal buttons, as the control is a StackLayout, so it can handle adding more controls.
Screenshot:
Also for Page inside another Page:
https://github.com/twintechs/TwinTechsFormsLib
http://blog.twintechs.com/advanced-xamarin-forms-techniques-for-flexible-and-performant-cross-platform-apps-part-5-page-in-page-embedding
Again thanks goes to IdoT.
You can take the parent child of your ContentPage (for example, a StackLayout that wraps all the children), put it in an external xaml file,
and then include that component in each one of your ContentPages.
The external xaml file will be a StackLayout type, rather than a ContentPage.
Edit - added a code sample:
Let's add a header StackLayout: we add a code behind class:
public partial class HeaderNavigationBar
{
public HeaderNavigationBar()
{
InitializeComponent();
}
}
Then add the XAML code:
<StackLayout x:Class="HeaderNavigationBar"
Orientation="Horizontal"
HorizontalOptions="FillAndExpand"
Padding="10"
BackgroundColor="White">
<Image Source="burger_icon"
HorizontalOptions="StartAndExpand"
Aspect="AspectFit">
<Image.GestureRecognizers>
<TapGestureRecognizer Command="{Binding SlideNavigationDrawerCommand}" />
</Image.GestureRecognizers>
</Image>
</StackLayout>
And finally, in the page where you want to reuse your component - add this reference:<HeaderNavigationBar />.

MVVM Light Toolkit: Is it possible to Send a message in the xaml via a trigger?

Is it possible to Send a message in the xaml via a trigger?
thanks.
I assume you're referring to MVVM Light Toolkit's messaging. If so, then No, this isn't possible.
However, it is not the responsibility of the View to do things like sending messages. The view's responsibility is to provide a view to the data and interaction with the user. Instead, you should have your ViewModel sending the message. You can "trigger" the message with a command in your view, but the command execute is what actually sends the message.
Here's an example View (MainPage.xaml) that has a couple of different ways to execute a command.
<UserControl x:Class="MvvmLight5.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.SL4" mc:Ignorable="d"
DataContext="{Binding Main, Source={StaticResource Locator}}">
<StackPanel x:Name="LayoutRoot">
<TextBlock Text="Click Here to Send Message">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonUp">
<cmd:EventToCommand Command="{Binding Path=SendMessageCommand, Mode=OneWay}"
CommandParameter="Sent From TextBlock" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBlock>
<Button Content="Or Click Here"
Command="{Binding Path=SendMessageCommand, Mode=OneWay}"
CommandParameter="Sent From Button" />
</StackPanel>
</UserControl>
And here's the MainViewModel.cs that sends the message.
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Messaging;
namespace MvvmLight5.ViewModel
{
public class MainViewModel : ViewModelBase
{
public RelayCommand<string> SendMessageCommand { get; private set; }
public MainViewModel()
{
SendMessageCommand = new RelayCommand<string>(SendMessageCommandExecute);
}
private void SendMessageCommandExecute(string sentFrom)
{
Messenger.Default.Send(new NotificationMessage(sentFrom));
}
}
}
Also, I should note that these were created in Silverlight 4.

Resources