Im using mvvm and viewmodel locator.Im using button commands or listview itemtap behaviors with no problem.But in one of my page I need to use external itemtemplate (resources).In this template i can bind labels with no problem.But I cant Bind command of button,I get this error "Can't resolve name on Element" .
here is the external custom cell
<ViewCell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:core="clr-namespace:paux;assembly=paux"
xmlns:controls="clr-namespace:paux.Controls;assembly=paux"
xmlns:xlabs="clr-namespace:XLabs.Forms.Controls;assembly=XLabs.Forms"
xmlns:base="clr-namespace:paux.Base;assembly=paux"
x:Class="paux.Controls.Cells.CustomDonemCell"> <ViewCell.View>
<Grid
BackgroundColor="{StaticResource WhiteColor}" Margin="0,0,0,0">
<Grid Grid.Column="1" Grid.Row="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackLayout
Grid.Column="1"
Margin="0,16,0,0"
Orientation="Vertical"
Spacing="0"
VerticalOptions="Start"> -
<Button Command="{Binding Path=BindingContext.mybuttonClicked, Source={x:Reference Name=mylistView}}" CommandParameter="{Binding id}" Text="My Button"/>
<controls:MultiLineLabel Text="{Binding BolumAdi}" Lines="2" VerticalOptions="Center" HorizontalOptions="Center" LineBreakMode="TailTruncation"
Margin="0,0,0,3"/>
</StackLayout>
</Grid>
</Grid>
</Grid> </ViewCell.View> </ViewCell>
This is the Page ,with templateselector (its working fine)
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:behavior="clr-namespace:paux.Behavior;assembly=paux"
xmlns:animations="clr-namespace:paux.Animations;assembly=paux"
xmlns:triggers="clr-namespace:paux.Triggers;assembly=paux"
xmlns:effects="clr-namespace:paux.Effects;assembly=paux"
xmlns:templateSelectors="clr-namespace:paux.TemplateSelectors;assembly=paux"
xmlns:converters="clr-namespace:paux.Converters;assembly=paux"
x:Class="paux.Pages.PageOgrenciDonem" >
<ContentPage.Resources>
<ResourceDictionary>
<templateSelectors:DataTemplateSelector x:Key="ogrenciDonemTemplate" />
</ResourceDictionary>
</ContentPage.Resources>
<Grid>
<ListView
x:Name="mylistView"
CachingStrategy="RecycleElement"
ItemsSource="{Binding OgrencilikList, Mode=OneWay}"
HasUnevenRows="True"
SeparatorVisibility="None"
ItemTemplate="{StaticResource ogrenciDonemTemplate}" >
<ListView.Margin>
<OnPlatform x:TypeArguments="Thickness"
Android="8"
WinPhone="8"/>
</ListView.Margin>
</ListView>
</Grid>
</ContentPage>
and in the viewmodel
public static readonly BindableProperty TestCommandProperty =
BindableProperty.Create("TestCommand", typeof(ICommand), typeof(CustomDonemCell), null);
public ICommand TestCommand => new Command<detay>(testclickevent);
private async void testclickevent(detay item)
{ await NavigationService.NavigateToAsync<detayviewmodel(item.id.ToString());
}
The problem you have is where to define your handler. You defined it in the PageTestViewModel but in xaml this command is not defined for the list or page model. It is defined for the ITEM. So, you have 3 options. You don't have to define both OnButtonClicked and TestCommand, I just show it as an option. If you define both you will get called in 2 places (see below)
<Button Clicked="OnButtonClicked" Command="{Binding TestCommand}" CommandParameter="{Binding id}" Text="My Button"/>
To be called via OnButtonClicked
public partial class CustomCell : ViewCell
{
public CustomCell()
{
InitializeComponent();
}
void OnButtonClicked(object sender, EventArgs args)
{
}
}
To be called in item
public class testdata
{
public string id { get; set; }
public Command TestCommand
{
get
{
return new Command((o) =>
{
System.Diagnostics.Debug.WriteLine("Item " + o.ToString());
});
}
}
}
To be called in PageTestViewModel where you wanted to be called you need to specify path to your model. This is more complicated. Send me a message if previous 2 methods don't work for you. But this is going to be tricky as you have your ViewCell xaml in separate file, so you cannot access page name or list name. I am not sure if it is good design to specify handler in cell which leaves in the top-level model. You might want to use one of 2 handlers I suggested and subscribe for events which will be fired from those handlers up.
Related
I'm trying to leverage a custom control within a CollectionView and would like to pass the entire object of the particular CollectionView ItemTemplate into my custom control.
Here's my xaml page:
<CollectionView ItemsSource="{Binding WorkOps}" SelectionMode="None" ItemsLayout="VerticalList">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="75" />
<ColumnDefinition Width="15" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Label Grid.Column="0"
Text="{Binding OpType}"
FontSize="Caption"
VerticalTextAlignment="Center"/>
<Label Grid.Column="1"
Text="{Binding OpNumber}"
FontSize="Caption"
VerticalTextAlignment="Center"/>
<Label Grid.Column="2"
Text="{Binding Instructions}"
FontSize="Body"/>
<Entry Grid.Column="2"
Grid.Row="1"
Text="{Binding Measure}"
IsVisible="{Binding IsSimpleMeasure}" />
<root:TableMeasureView Grid.Column="2"
Grid.Row="1"
Op="{Binding .}"
IsVisible="{Binding IsTableMeasure}" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
and here is my custom control I'm trying to implement:
public class TableMeasureView : Grid
{
public static readonly BindableProperty WorkOpProperty =
BindableProperty.Create(nameof(Op), typeof(WorkOp), typeof(ContentPage));
public WorkOp Op
{
get { return (WorkOp)GetValue(WorkOpProperty); }
set { SetValue(WorkOpProperty, value); }
}
public TableMeasureView()
{
}
// ...
}
I get the following message when trying to build:
XamlC error XFC0009: No property, BindableProperty, or event found for "Op", or mismatching type between value and property.
Is what I'm attempting possible?
Yes it is possible. What's happening is that the xaml doesn't attempt to figure out that the type of {Binding .} is WorkOp. It wants a property of type object.
The fix is to give it a property of type object. Then for convenient access in your custom control, make a second property that casts that to WorkOp:
public static readonly BindableProperty WorkOpProperty =
BindableProperty.Create(nameof(Op), typeof(object), typeof(ContentPage));
public object Op
{
get { return GetValue(WorkOpProperty); }
set { SetValue(WorkOpProperty, value); }
}
private WorkOp TypedOp => (WorkOp)Op;
NOTE: Change Op and TypedOp above to whatever names you like. If you change Op, remember to also change in the xaml that refers to it.
Here is a picture of my file structure and MainPage.Xaml file
MainPage.xaml Here is the code in that file
<?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:local="clr-namespace:AppXam"
x:Class="AppXam.MainPage">
<ContentPage.BindingContext>
<local:MainPage/>
</ContentPage.BindingContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="2*"/>
<RowDefinition Height=".5*"/>
<RowDefinition Height="2*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image Source="https://s2.dmcdn.net/v/AmZGT1VXLk5NDRThE/x1080" BackgroundColor="red"
Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="2"/>
<Editor Grid.Column ="0" Grid.ColumnSpan="2" Grid.Row="1"
Placeholder="Enter Note Here" Text="{Binding TheNote}" />
<Button Grid.Row="2" Grid.Column="0" Text="Save" Command="{Binding SaveCommand}" />
<Button Grid.Row="2" Grid.Column="1" Text="Erase" Command="{Binding EaseCommand}"/>
<CollectionView ItemsSource="{Binding AllNotes}" Grid.Row="3" Grid.ColumnSpan="2">
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout>
<Frame>
<Label Grid.Row="3" Grid.Column="0" Text="{Binding .}" FontSize="Large" />
</Frame>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</Grid>
Here is a picture of the MainPage.XAML.cs which is the namespace that contains the MVVM code.
MainPage.xaml.cs
The code is as follows :
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace AppXam
{
public partial class MainPage : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public MainPage()
{
EraseCommand = new Command(() =>
{
TheNote = string.Empty;
});
SaveCommand = new Command(() =>
{
AllNotes.Add(TheNote);
TheNote = string.Empty;
});
}
public ObservableCollection<string> AllNotes {get; set;}
string theNote;
public string TheNote
{
get => theNote;
set
{
theNote = value;
var args = new PropertyChangedEventArgs(nameof(TheNote));
PropertyChanged?.Invoke(this,args);
}
}
public Command SaveCommand { get; }
public Command EraseCommand { get; }
}
}
The screenshots contain the output on the screen, which is a blank white page. The project directory can be found here
https://drive.google.com/drive/folders/1sp4qh3Wzse1KVZN1IKdteaGcgncCG4p4?usp=sharing
Remove the binding in the XAML and use this:
public MainPage()
{
InitializeComponent(); //1. no init, no page
BindingContext = this; //2. must bind after init
//other things to do
}
Please, clean the solution (remove the bin & obj folders) before uploading next time.
I'm currently in the process of implementing a menu navigation system for a group project. We're using Caliburn.Micro and the MVVM pattern.
The menu is located in the ShellView.xaml and is based on a List<NavigationMenuItem> that has been filtered using the Role property.
public class NavigationMenuItem
{
public IScreen ViewModel { get; set; }
public string IconPath { get; set; }
public string Role { get; set; }
public NavigationMenuItem(IScreen viewModel, string iconPath, string role)
{
ViewModel = viewModel;
IconPath = iconPath;
Role = role;
}
}
The menu is displaying just fine, but now I want to pass the ViewModel-property to a method in the ShellViewModel.cs, such that I can activate a view change. This is how the ShellView.xaml looks so far:
<Window
x:Class="P3GG.Views.ShellView"
xmlns:cal="http://www.caliburnproject.org"
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:local="clr-namespace:P3GG.Views"
xmlns:models="clr-namespace:P3GG.Models"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:templates="clr-namespace:P3GG.Templates"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
Title="Title"
Width="800"
Height="600"
WindowStartupLocation="CenterScreen"
mc:Ignorable="d">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ListBox Visibility="{Binding SidebarIsVisible, Converter={StaticResource BooleanToVisibilityConverter}, FallbackValue=Collapsed}"
Grid.Column="0"
Background="#333333"
x:Name="NavigationMenu">
<ListBox.ItemTemplate>
<DataTemplate DataType="models:NavigationMenuItem">
<Button cal:Message.Attach="Handle( <!-- pass content of NavigationMenuItem.ViewModel --> )" Style="{StaticResource BtnSidebar}">
<Image Margin="20" Source="{Binding IconPath}"/>
</Button>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ContentControl Grid.Column="1" x:Name="ActiveItem"/>
</Grid>
</Window>
The ShellViewModel.cs currently contains two Handle methods:
public void Handle(Screen screen)
public void Handle(User user)
How do I pass the ViewModel property of a given NavigationMenu to the Handle(Screen) method?
EDIT
Added Handle method per request
public void Handle(Screen screen)
{
if (screen is LoginViewModel)
{
SidebarIsVisible = false;
NotifyOfPropertyChange(() => SidebarIsVisible);
}
else
{
SidebarIsVisible = true;
NotifyOfPropertyChange(() => SidebarIsVisible);
}
ActivateItem(screen);
}
Maybe you can try something like this, as explained here:
<Button cal:Message.Attach="[Event Click] = [Action Handle($dataContext)]" Style="{StaticResource BtnSidebar}">
<Image Margin="20" Source="{Binding IconPath}"/>
</Button>
This will pass the full view model (DataContext) to your handler.
To pass a specific property of your view model, use the long syntax as indicated here:
<Button Style="{StaticResource BtnSidebar}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cal:ActionMessage MethodName="Handle">
<cal:Parameter Value="{Binding ViewModel}" />
</cal:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
<Image Margin="20" Source="{Binding IconPath}"/>
</Button>
with i defined as
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
You could also change the DataContext property of the Button to the property of interest, like:
<Button DataContext="{Binding ViewModel}" cal:Message.Attach="[Event Click] = [Action Handle($dataContext)]" Style="{StaticResource BtnSidebar}">
<Image Margin="20" Source="{Binding IconPath}"/>
</Button>
so only the correct property gets passed to your hendler, but this feels like a hack.
On click, this will hand-over the DataContext of the Button as parameter of your Handle method, which should be a NavigationMenuItem.
I have a WPF application and just embarked in learning MVVM pattern.
My goal is that in my application the main window has a button. When this button is clicked, another window (or user control) will appear on top of the main window.
This is the code of MainWindow.xaml
<Window x:Class="SmartPole1080.View.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:utilities="clr-namespace:SoltaLabs.Avalon.Core.Utilities;assembly=SoltaLabs.Avalon.Core"
xmlns:userControls="clr-namespace:SoltaLabs.Avalon.View.Core.UserControls;assembly=SoltaLabs.Avalon.View.Core"
xmlns:controls="clr-namespace:WpfKb.Controls;assembly=SmartPole.WpfKb"
xmlns:wpf="clr-namespace:WebEye.Controls.Wpf;assembly=WebEye.Controls.Wpf.WebCameraControl"
xmlns:view="clr-namespace:SmartPole.View;assembly=SmartPole.View"
xmlns:view1="clr-namespace:SmartPole1080.View"
xmlns:behaviors="clr-namespace:SoltaLabs.Avalon.Core.Behaviors;assembly=SoltaLabs.Avalon.Core"
Title="Smart Pole"
WindowStartupLocation="CenterScreen"
Name="mainWindow"
behaviors:IdleBehavior.IsAutoReset="True" WindowState="Maximized" WindowStyle="None">
<Canvas Background="DarkGray">
<!--Main Grid-->
<Grid Width="1080" Height="1920" Background="White" Name="MainGrid"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<StackPanel Background="Black">
<Grid Background="#253341">
<Grid.RowDefinitions>
<RowDefinition Height="5"/>
<RowDefinition Height="*"/>
<RowDefinition Height="5"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="5"/>
<ColumnDefinition Width="264"/>
</Grid.ColumnDefinitions>
<Grid Grid.Row="1" Grid.Column="1">
<Button Tag="{StaticResource EmergencyImg}" Name="EmergencyButton"
Command="{Binding ShowEmergencyPanel}">
<Image Source="{StaticResource EmergencyImg}" />
</Button>
</Grid>
<!--Emergency Window Dialog-->
<Grid Name="EmergencyPanel">
<view1:EmergencyInfo x:Name="EmergencyInfoPanel"/>
</Grid>
</Grid>
</StackPanel>
</Grid>
<!--Main Grid-->
</Canvas>
This is the other window (user control - EmergencyInfo.xaml)
<UserControl x:Class="SmartPole1080.View.EmergencyInfo"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:SmartPole1080.View"
mc:Ignorable="d"
d:DesignHeight="1920" d:DesignWidth="1050"
x:Name="EmergencyInfoPanel">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Border Grid.Row="0" BorderBrush="White" Background="White">
<Button Background="White" BorderThickness="0" FontWeight="Bold" Foreground="Red"
HorizontalAlignment="Right" FontSize="25" Margin="0,0,15,0"
Command="{Binding HideEmergencyPanel}">
close X
</Button>
</Border>
<Image Grid.Row="1" Source="{StaticResource EdenParkInfoImg}" HorizontalAlignment="Left" />
</Grid>
I want to implement this behavior using an MVVM pattern. I have set the binding ShowEmergencyPanel in button EmergencyButton to show EmergencyInfo when this button is click.
Any help is greatly appreciated.
Why dont you make navigation, something like this. Make section for conetent that will be injected, and whatever type of object you are expecting put it in Windows.Resources in DataTemplate.
In main windo xaml
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type home:HomeViewModel}">
<home:HomeView />
</DataTemplate>
<DataTemplate DataType="{x:Type other:OtherViewModel}">
<other:OtherView />
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid x:Name="Navigation">
<StackPanel Orientation="Horizontal">
<Button x:Name="HomeView"
Content="Home"
Margin="5"
Command="{Binding NavigationCommand}"
CommandParameter="home" />
<Button x:Name="Menu"
Content="OtherView"
Margin="5"
Command="{Binding NavigationCommand}"
CommandParameter="Other" />
</StackPanel>
</Grid>
<Grid x:Name="MainContent"
Grid.Row="1">
<ContentControl Content="{Binding CurrentViewModel}" />
</Grid>
</Grid>
MainWindowViewModel can look something like this.
public class MainWindowViewModel : INotifyPropertyChanged
{
private OtherViewModel otherVM;
private HomeViewModel homeVM;
public DelegateCommand<string> NavigationCommand { get; private set; }
public MainWindowViewModel()
{
otherVM = new OtherViewModel();
homeVM = new HomeViewModel();
// Setting default: homeViewModela.
CurrentViewModel = homeVM;
NavigationCommand = new DelegateCommand<string>(OnNavigate);
}
private void OnNavigate(string navPath)
{
switch (navPath)
{
case "other":
CurrentViewModel = otherVM;
break;
case "home":
CurrentViewModel = homeVM;
break;
}
}
private object _currentViewModel;
public object CurrentViewModel
{
get { return _currentViewModel; }
set
{
if (_currentViewModel != value)
{
_currentViewModel = value;
OnPropertyChanged();
}
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged(this, new propertyChangedEventArgs(propertyName));
}
#endregion
}
Where DelegateCommand you can make yours,check how to make RelayCommand(and generic one) or use PRISM that have it's own DelegateCommand. But if you want to use PRISM, it allready has navigation to regions, that can solve many problems. Check videos from Brian Lagunas.
EDIT:
This is to show/hide grid. In your mainWindow where you set that EmergencyInfo u can show/hide that grid this way.
in your MainViewViewModel make a bool property IsVisible
private bool _isVisible;
public bool IsVisible
{
get { return _isVisible; }
set
{
_isVisible = value;
OnPropertyChanged();
}
}
in your MainView.Resources set key to BooleanToVisibilityConverter
something like:
<BooleanToVisibilityConverter x:Key="Show"/>
and your grid that you want to show/hide set visibility:
<Grid x:Name="SomeGridName"
Grid.Row="1"
Grid.Colum="1"
Visibility="{Binding IsVisible,Converter={StaticResource Show}}">
And finally set that IsVisible property to some ToggleButton, just to switch between true/false
<ToggleButton IsChecked="{Binding IsVisible}"
Content="ShowGrid" />
This way, you show/hide that grid part based on IsVisible, and you control that visibility onClick. Hope that helps.
Inside your Window xaml:
<ContentPresenter Content="{Binding Control}"></ContentPresenter>
In this way, your ViewModel must contain a Control property.
Add your ViewModel to DataContext of Window.
(For example in window constructor, this.Datacontext = new ViewModel();)
Or another way with interfaces:
public interface IWindowView
{
IUserControlKeeper ViewModel { get; set; }
}
public interface IUserControlKeeper
{
UserControl Control { get; set; }
}
public partial class YourWindow : IViewWindow
{
public YourWindow()
{
InitializeComponent();
}
public IUserControlKeeper ViewModel
{
get
{
return (IUserControlKeeper)DataContext;
}
set
{
DataContext = value;
}
}
}
(In this way, initialize your window where you want to use. Service?)
private IViewWindow _window;
private IViewWindow Window //or public...
{
get{
if(_window==null)
{
_window = new YourWindow();
_window.ViewModel = new YourViewModel();
}
return _window;
}
}
Open your window with one of your UserControls:
void ShowWindowWithControl(object ControlView, INotifyPropertyChanged ControlViewModel, bool ShowAsDialog)
{
if(ControlView is UserControl)
{ //with interface: Window.ViewModel.Control = ...
(Window.DataContext as YourViewModel).Control = (UserControl)ControlView;
if (ControlViewModel != null)
(Window.DataContext as YourViewModel).Control.DataContext = ControlViewModel;
if (ShowAsDialog) //with interface use cast to call the show...
Window.ShowDialog();
else
Window.Show();
}
}
What you can do is, place the Emergency usercontrol inside MainGrid and control its visibility through Model property.
<Canvas Background="DarkGray">
<!--Main Grid-->
<Grid Width="1080" Height="1920" Background="White" Name="MainGrid"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<StackPanel Background="Black">
<Grid Background="#253341">
<Grid.RowDefinitions>
<RowDefinition Height="5"/>
<RowDefinition Height="*"/>
<RowDefinition Height="5"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="5"/>
<ColumnDefinition Width="264"/>
</Grid.ColumnDefinitions>
<Grid Grid.Row="1" Grid.Column="1">
<Button Tag="{StaticResource EmergencyImg}" Name="EmergencyButton"
Command="{Binding ShowEmergencyPanel}">
<Image Source="{StaticResource EmergencyImg}" />
</Button>
</Grid>
</Grid>
</StackPanel>
<Grid Name="EmergencyPanel">
<view1:EmergencyInfo x:Name="EmergencyInfoPanel" Visibility={Binding IsEmergencyPanelVisible,Converter={StaticResource BoolToVisibilityConverter}} DataContext={Binding} />
</Grid>
</Grid>
<!--Main Grid-->
</Canvas>
By setting proper background to Grid that holding usercontrol, you can achieve modal window like effect.
And you need to have IsEmergencyPanelVisible(default value is false) is your Model, and this property should be changed to true on your button click command.
Note : I guess you are familiar with converters, so i included Converters in my solution.
Disclaimer: I'm new to Xamarin.Forms and Xaml in general
I'm trying to figure out how DataBinding works within templates in Xamarin.Forms.
Xaml:
<ContentPage.Resources>
<ResourceDictionary>
<ControlTemplate x:Key="GridItem">
<Grid HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" Padding="0" ColumnSpacing="0" RowSpacing="0">
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<ContentView Grid.Row="0" Grid.Column="0" BackgroundColor="Red" VerticalOptions="FillAndExpand" Padding="15">
<Image Source="{TemplateBinding ImageSource}" BackgroundColor="Red" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand" />
</ContentView>
<Label Grid.Row="1" Grid.Column="0" Text="{TemplateBinding LabelText}" FontSize="Large" TextColor="White" BackgroundColor="#8B0000" HorizontalTextAlignment="Center" VerticalTextAlignment="Center" />
</Grid>
</ControlTemplate>
</ResourceDictionary>
</ContentPage.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="1*" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<ContentView x:Name="randomButton1" ControlTemplate="{StaticResource GridItem}" Grid.Row="0" Grid.Column="0" />
<ContentView x:Name="randomButton2" ControlTemplate="{StaticResource GridItem}" Grid.Row="0" Grid.Column="1" />
</Grid>
C# Code behind
using Xamarin.Forms;
namespace TestApp.XForms
{
public partial class Page1 : ContentPage
{
private readonly MainButtonViewModel[] buttons;
public Page1()
{
InitializeComponent();
randomButton1.BindingContext = new MainButtonViewModel("some text1", "someimage1.png");
randomButton2.BindingContext = new MainButtonViewModel("some text2", "someimage2.png");
}
}
public class MainButtonViewModel : BindableObject
{
private string labelText;
public string LabelText
{
get { return labelText; }
set
{
labelText = value;
OnPropertyChanged();
}
}
private string imageSource;
public string ImageSource
{
get { return imageSource; }
set
{
imageSource = value;
OnPropertyChanged();
}
}
public MainButtonViewModel()
{
}
public MainButtonViewModel(string text, string imageLocation)
{
LabelText = text;
ImageSource = imageLocation;
}
}
}
So my templates seem to work. I see 2 red blocks. But none of the bindings seem to have worked. Everything is empty:
How can I get the data binding to work?
Your view model doesn't support INotifyPropertyChanged. You could use Xamarin.Forms class:
public class SomeViewModel : BindableObject
And then you could notify UI, that some property has been changed:
private string _someStringProperty;
public string SomeStringProperty
{
get { return _someStringProperty; }
set
{
_someStringProperty = value;
OnPropertyChanged();
}
}
Also will be helpful Binding from ControlTemplate.
When you use ControlTemplate it requires TemplateBinding for BindingContext, otherwise it won't have any BindingContext itself. You can use DataTemplate without setting BindingContext, it inherits parent's BindingContext itself.
i.e:
<ControlTemplate x:Key="yourTemplate" BindingContext="{TemplateBinding BindingContext}">
<Grid>
<!--Your Template-->
</Grid>
</ControlTemplate>
<DataTemplate x:Key="yourTemplate">
<Grid>
<!--Your Template-->
</Grid>
</DataTemplate>