Xamarin.Forms xaml views, navigation and binding - binding

I am trying to setup a simple main page with a title and status bar, and dynamically change the middle content.
I'd like to keep the views in xaml as much as possible.
The issue I am having is with binding. The MainView bindings work fine.
But the bindings to my title, status, and page views don't work.
Am I setting up the navigation correctly?
Here's my 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:local="clr-namespace:MyApp.Views"
x:Class="MyApp.Views.MainView">
<ContentPage.Content>
<Grid HorizontalOptions="FillAndExpand">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.Children>
<Button Grid.Row="0" Grid.Column="0"
Text="Show Menu"
Command="{Binding ShowMenuCommand}"
BackgroundColor="Yellow" />
<local:TitleBarView Grid.Row="0" Grid.Column="1"/>
<Button Grid.Row="0" Grid.Column="2"
Text="Show Options"
Command="{Binding ShowOptionsCommand}"
BackgroundColor="Lime"/>
<ContentView Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3"
x:Name="MainContent" />
<local:StatusBarView Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="3"/>
</Grid.Children>
</Grid>
</ContentPage.Content>
</ContentPage>
My TitleBarView.xaml
<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyApp.Views.TitleBarView">
<ContentView.Content>
<StackLayout Orientation="Horizontal" BackgroundColor="Blue">
<Label Text="TitleInfo1: " TextColor="White"/>
<Label Text="{Binding TitleInfo1}" TextColor="White"/>
<Label Text="TitleInfo2: " TextColor="White"/>
<Label Text="{Binding TitleInfo2}" TextColor="White"/>
</StackLayout>
</ContentView.Content>
</ContentView>
My StatusBarView.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyApp.Views.StatusBarView">
<ContentView.Content>
<StackLayout Orientation="Horizontal" BackgroundColor="Green">
<Label Text="StatusInfo1: " TextColor="White"/>
<Label Text="{Binding StatusInfo1}" TextColor="White"/>
<Label Text="StatusInfo2: " TextColor="White"/>
<Label Text="{Binding StatusInfo2}" TextColor="White"/>
</StackLayout>
</ContentView.Content>
</ContentView>
My Page1View.xaml
<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyApp.Views.Page1View">
<ContentView.Content>
<StackLayout Orientation="Vertical">
<Label Text="{Binding Page1Info}"/>
<Button Text="Go To Page 2" Command="{Binding GoToPage2Command}"/>
</StackLayout >
</ContentView.Content>
</ContentView>
My Page2View.xaml
<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyApp.Views.Page2View">
<ContentView.Content>
<StackLayout Orientation="Vertical">
<Label Text="{Binding Page2Info}"/>
<Button Text="Go To Page 1" Command="{Binding GoToPage1Command}"/>
<Button Text="Go To Page 3" Command="{Binding GoToPage3Command}"/>
</StackLayout >
</ContentView.Content>
</ContentView>
etc...
My base view model class that implements notification
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}
My MainViewModel.cs
class MainViewModel : ViewModel
{
private const string TAG = "MainViewModel";
public MainViewModel()
{
Logger.mt(TAG, "MainViewModel()");
}
#region ShowMenuCommand
private Command _showMenuCommand;
public ICommand ShowMenuCommand
{
get
{
if (_showMenuCommand == null)
_showMenuCommand = new Command(param => this.showMenuClick());
return _showMenuCommand;
}
}
public void showMenuClick()
{
Logger.ui(TAG, "showMenuClick()");
//ToDo: show main menu
}
#endregion
#region ShowOptionsCommand
private Command _showOptionsCommand;
public ICommand ShowOptionsCommand
{
get
{
if (_showOptionsCommand == null)
_showOptionsCommand = new Command(param => this.showOptionsClicked());
return _showOptionsCommand;
}
}
public void showOptionsClicked()
{
Logger.ui(TAG, "showOptionsClicked()");
//ToDo: show options menu
}
#endregion
}
My TitleBarViewModel.cs
class TitleBarViewModel : ViewModel
{
private const string TAG = "TitleBarViewModel";
public TitleBarViewModel()
{
Logger.mt(TAG, "TitleBarViewModel()");
}
#region TitleInfo1
private string _titleInfo1 = "This is title info 1";
public string TitleInfo1
{
get { return _titleInfo1; }
set
{
if (!value.Equals(_titleInfo1))
{
_titleInfo1 = value;
OnPropertyChanged("TitleInfo1");
}
}
}
#endregion
#region TitleInfo2
private int _titleInfo2 = 14;
public int TitleInfo2
{
get { return _titleInfo2; }
set
{
if (value != _titleInfo2)
{
_titleInfo2 = value;
OnPropertyChanged("TitleInfo2");
}
}
}
#endregion
}
My StatusBarViewModel.cs
class StatusBarViewModel : ViewModel
{
private const string TAG = "StatusBarViewModel";
public StatusBarViewModel()
{
Logger.mt(TAG, "StatusBarViewModel()");
}
#region StatusInfo1
private string _statusInfo1 = "This is Status Info 1";
public string StatusInfo1
{
get { return _statusInfo1; }
set
{
if (!value.Equals(_statusInfo1))
{
_statusInfo1 = value;
OnPropertyChanged("StatusInfo1");
}
}
}
#endregion
#region StatusInfo2
private string _statusInfo2 = "This is Status Info 2";
public string StatusInfo2
{
get { return _statusInfo2; }
set
{
if (!value.Equals(_statusInfo2))
{
_statusInfo2 = value;
OnPropertyChanged("StatusInfo2");
}
}
}
#endregion
}
My Page1ViewModel.cs
class Page1ViewModel : ViewModel
{
private const string TAG = "Page1ViewModel";
public Page1ViewModel()
{
Logger.mt(TAG, "Page1ViewModel()");
}
#region Page1Info
private string _page1Info = "This is Page 1 Info";
public string Page1Info
{
get { return _page1Info; }
set
{
if (!value.Equals(_page1Info))
{
_page1Info = value;
OnPropertyChanged("Page1Info");
}
}
}
#endregion
#region Go To Page 2
private Command _goToPage2Command;
public ICommand GoToPage2Command
{
get
{
if (_goToPage2Command == null)
_goToPage2Command = new Command(param => this.goToPage2Clicked());
return _goToPage2Command;
}
}
public void goToPage2Clicked()
{
Logger.ui(TAG, "goToPage2Clicked()");
NavigationController.setContentPage(typeof(Page2View));
}
#endregion
}
etc ...
My NavigationController.cs
public class NavigationController
{
private const string TAG = "NavigationController";
static MainView mainView;
static MainViewModel mainViewModel;
static TitleBarView titleBarView;
static TitleBarViewModel titleBarViewModel;
static StatusBarView statusBarView;
static StatusBarViewModel statusBarViewModel;
static List<ContentView> viewList = new List<ContentView>();
static ContentView currentView;
public static void Initialize()
{
Logger.mt(TAG, "Initialize()");
mainView = new MainView();
mainViewModel = new MainViewModel();
mainView.BindingContext = mainViewModel;
titleBarView = new TitleBarView();
titleBarViewModel = new TitleBarViewModel();
titleBarView.BindingContext = titleBarViewModel;
statusBarView = new StatusBarView();
statusBarViewModel = new StatusBarViewModel();
statusBarView.BindingContext = statusBarViewModel;
Page1View view1 = new Page1View();
Page1ViewModel viewModel1 = new Page1ViewModel();
view1.BindingContext = viewModel1;
viewList.Add(view1);
Page2View view2 = new Page2View();
Page2ViewModel viewModel2 = new Page2ViewModel();
view2.BindingContext = viewModel2;
viewList.Add(view2);
Page3View view3 = new Page3View();
Page3ViewModel viewModel3 = new Page3ViewModel();
view3.BindingContext = viewModel3;
viewList.Add(view3);
mainView.setMainContent(view1);
currentView = view1;
}
public static Page getMainPage()
{
Logger.mt(TAG, "getMainPage()");
return mainView;
}
public static void setContentPage(Type type)
{
Logger.mt(TAG, "setContentPage(" + type.ToString() + ")");
foreach (ContentView cv in viewList)
{
if (cv.GetType() == type)
{
if (cv != currentView)
{
mainView.setMainContent(cv);
currentView = cv;
Logger.mt(TAG, "setContentPage() FOUND");
return;
}
}
}
Logger.mt(TAG, "setContentPage() NOT FOUND");
}
}
And to set the view, I edited the MainView.xaml.cs
public partial class MainView : ContentPage
{
private const string TAG = "MainView";
public MainView()
{
Logger.mt(TAG, "MainView()");
InitializeComponent();
}
public bool setMainContent(ContentView view)
{
Logger.mt(TAG, "setMainContent()");
MainContent.Content = view.Content;
return true;
}
}

I think Xamarin.Forms provides something that you are looking for. It's called templates. Check it out: https://developer.xamarin.com/guides/xamarin-forms/templates/control-templates/introduction/

Related

Return listview value from ItemTapped to filter firebase call - Selected item returns as null

I have tried adding the data to Listview using the public class 'Videotag' and a list 'initialList'. I'm trying to compare the listview selected item value to tags data from the RealtimeFirebase. The data from the firebase calls fine, but selected item value from listview returns null. Anyone know what i'm doing wrong?
public partial class videosection : ContentPage
{
List<videos> allVideos = new List<videos>();
List<Videotag> item = new List<Videotag>();
//List<string> initialList = new List<string>();
public videosection()
{
InitializeComponent();
//DependencyService.Get<IRotate>().ForcePortrait();
//initialList.Add("Shoulders");
//initialList.Add("Core");
//initialList.Add("Arms");
//initialList.Add("Hands");
//initialList.Add("Legs");
//initialList.Add("Balance");
//VideoLV.ItemsSource = initialList;
item.Add(new Videotag() { tags = "Shoulders" });
item.Add(new Videotag() { tags = "Core" });
item.Add(new Videotag() { tags = "Arms" });
item.Add(new Videotag() { tags = "Hands" });
item.Add(new Videotag() { tags = "Legs" });
item.Add(new Videotag() { tags = "Balance" });
VideoLV.ItemsSource = item;
//VideoLV.selectedi = item[0];
getvideo();
}
public class Videotag
{
public string tags { get; set; }
}
async private void getvideo()
{
allVideos = await DbFirebase.Getvideos();
//Adds videos to ItemsSource depending on the currently selected tag
//videosListView.ItemsSource = allVideos.Where(v => v.tag.Trim().ToLower() == (VideoLV.SelectedItem).ToString().Trim().ToLower()).ToList();
}
//private void SfChipGroup_SelectionChanged(object sender, Syncfusion.Buttons.XForms.SfChip.SelectionChangedEventArgs e)
//{
// videosListView.ItemsSource = allVideos.Where(v => v.tag.Trim().ToLower() == ((SfChip)chips.SelectedItem).Text.Trim().ToLower()).ToList();
//}
private async void Watch_Button_Clicked(object sender, EventArgs e)
{
string videoId = (string)((ImageButton)sender).BindingContext;
var video = allVideos.FirstOrDefault(v => v.id == videoId);
await Navigation.PushModalAsync(new SingleVideo(video), false);
}
private void VideoLV_ItemTapped(object sender, Syncfusion.ListView.XForms.ItemTappedEventArgs e)
{
//var indexes = e.ToString();
//String selectedFromList = VideoLV.getItemAtPosition(position);
//int myindex = VideoLV.Index(tags);
//vaVideoLV.selectedItem
//var selected = (videotag)e.SelectedItem;
videosListView.ItemsSource = allVideos.Where(v => v.tag.Trim().ToLower() == ((SfListView)VideoLV.SelectedItem).ToString().Trim().ToLower()).ToList();
}
<syncfusion:SfListView x:Name="VideoLV"
Padding="0"
AutoFitMode="Height"
Margin="10,0,10,0"
ItemTapped="VideoLV_ItemTapped"
BackgroundColor="Transparent"
SelectionMode="Single"
VerticalOptions="StartAndExpand"
IsScrollBarVisible="False"
SelectionBackgroundColor="Transparent"
Orientation="Horizontal"
HeightRequest="55">
<syncfusion:SfListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Frame CornerRadius="10" Margin="5,0,5,0" BackgroundColor="LightBlue" HasShadow="False" >
<Grid HorizontalOptions="StartAndExpand" Margin="0" Padding="0" >
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Label Text="{Binding tags}" Margin="0,-5,0,0" Grid.Row="0" VerticalOptions="StartAndExpand" TextColor="Black" FontSize="Small" />
</Grid>
</Frame>
</ViewCell>
</DataTemplate>
</syncfusion:SfListView.ItemTemplate>
<syncfusion:SfListView.SelectedItemTemplate>
<DataTemplate>
<ViewCell>
<Frame CornerRadius="10" Margin="5,0,5,0" BackgroundColor="LightBlue" HasShadow="False" BorderColor="Red">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Label Text="{Binding tags}" Margin="0,-5,0,0" VerticalOptions="CenterAndExpand" Grid.Row="0" TextColor="Red" FontSize="Small" />
</Grid>
</Frame>
</ViewCell>
</DataTemplate>
</syncfusion:SfListView.SelectedItemTemplate>
</syncfusion:SfListView>
you are trying to cast SelectedItem to SfListView, which is wrong
(SfListView)VideoLV.SelectedItem
the SelectedItem should be a Videotag
(Videotag)VideoLV.SelectedItem

Xamarin Forms Listview will not display on my iPone. However, it does on the simulator

I have tried everything I could think of and everything I found on the web and have not had any luck, still a blank page.
here is my xaml file;
<ListView x:Name="AutoView" ItemsSource="{Binding AutoData}" SelectedItem="{Binding SelectedItem}" SeparatorVisibility="None" Grid.Row="1" Grid.ColumnSpan="6" BackgroundColor="Purple">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="27"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="65"/>
<ColumnDefinition Width="5"/>
<ColumnDefinition Width="55"/>
<ColumnDefinition Width="65"/>
<ColumnDefinition Width="60"/>
<ColumnDefinition Width="36"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Grid.Row="0" FontSize="Medium" TextColor="White" Text="{Binding Year}" HorizontalTextAlignment="Center"/>
<Label Grid.Column="2" Grid.Row="0" FontSize="Medium" TextColor="White" Text="{Binding Name}" HorizontalTextAlignment="Start" Grid.ColumnSpan="2"/>
<Switch x:Name="{Binding Id}" IsToggled="{Binding IsChecked, Mode=TwoWay}" Toggled="Handle_Toggled" Grid.Column="6" Grid.Row="0" Grid.ColumnSpan="2"/>
</Grid>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
My code in my view model looks like this, I have changed it multiple times but this is where it is at now.
public ObservableCollection<AutoWithSwitch> MyList = new ObservableCollection<AutoWithSwitch>();
public ObservableCollection<AutoWithSwitch> AutoData = new ObservableCollection<AutoWithSwitch>(); //{ get { return MyList; } }
private IPopupNavigation _popup { get; set; }
private PopupPage _modalPage;
public event PropertyChangedEventHandler PropertyChanged;
public UpdateCarsViewModel()
{
//Analytics.TrackEvent("Top of Constructor in UpdateCarsViewModel");
GetDisplayData();
Sort.SortOrder(MyList, i => i.Year, false);
_popup = PopupNavigation.Instance;
_modalPage = new PopupPage();
}
public void GetDisplayData()
{
MileageItemRepository repository = new MileageItemRepository();
var response = repository.GetAuto2();
//MyList.Clear();
foreach (var item in response)
{
Analytics.TrackEvent("Car Data GetDisplayData: carId " + item.Id + " Name = " + item.CarDesc + " Year = " + item.CarYear);
AutoData.Add(new AutoWithSwitch
{
Id = item.Id,
Year = item.CarYear,
Name = item.CarDesc,
IsChecked = item.IsDefault
});
Analytics.TrackEvent("In GetDisplayData in UpdateCarsViewModel CarYear = " + item.CarYear);
}
//AutoData = MyList;
}
As you can see I have logging in there and I can see the car information being retrieved from the DB table, it just will not display.
Per your code snippets, ListView controls is binding to AutoData of type ObservableCollection and the data in side of list view will be binding to the properties of the class AutoWithSwitch to the four properties:
Id,Year,Name and IsChecked.
So you should claim and use the AutoData with set and get property like below:
public class UpdateCarsViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<AutoWithSwitch> mylist;
public ObservableCollection<AutoWithSwitch> AutoData
{
get { return mylist; }
set { mylist = value; }
}
public UpdateCarsViewModel()
{
GetDisplayData();
}
public void GetDisplayData()
{
//get data logic
AutoData = new ObservableCollection<AutoWithSwitch>()
{
new AutoWithSwitch()
{
Id = "1",
Year = "2022",
Name = "Steve_Jobs",
IsChecked = false
}
};
}
}
}
Note:Make sure you can successfully retrieve data from below,
MileageItemRepository repository = new MileageItemRepository();
var response = repository.GetAuto2();
This was helpful and aswered my question. Hope this is what you meant by answering that it was helpful.

Can I populate a Label within my CollectionView with a value outside the ItemsSource List that populates the view?

I have a Label within my CollectionView that I need to populate with a value outside the ItemsSource List that populates the view.
The following code is an example of what I am trying to accomplish but it seems that the CollectionView is limiting the binding context to just the Items list. I have tried naming the label and setting it in my c# code but I cant seem to access the label in c#. I suppose I could build the whole page in c# rather than using the .xaml but unlike this example my actual code uses multiple templates and a template selector. If I could figure this out without rewriting hours of code I would prefer it.
ItemsPage.xaml
<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"
x:Class="TabTest.Views.ItemsPage"
Title="{Binding Title}"
x:Name="BrowseItemsPage">
<ContentPage.ToolbarItems>
<ToolbarItem Text="Add" Clicked="AddItem_Clicked" />
</ContentPage.ToolbarItems>
<StackLayout Padding="10">
<Label Text="{Binding TestVal}" FontSize="16" HeightRequest="20" />
<!-- ^^^^ This label displays just as expected -->
<RefreshView IsRefreshing="{Binding IsBusy, Mode=TwoWay}" Command="{Binding LoadItemsCommand}">
<CollectionView x:Name="ItemsCollectionView"
ItemsSource="{Binding Items}">
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout Padding="10">
<Label x:Name="TestV" Text="{Binding Path=BindingContext.TestVal}" />
<!-- ^^^^ I want this Label to display the TestVal string in the ViewModel -->
<Label Text="{Binding Text}"
d:Text="{Binding .}"
LineBreakMode="NoWrap"
Style="{DynamicResource ListItemTextStyle}"
FontSize="16" />
<Label Text="{Binding Description}"
d:Text="Item descripton"
LineBreakMode="NoWrap"
Style="{DynamicResource ListItemDetailTextStyle}"
FontSize="13" />
<StackLayout.GestureRecognizers>
<TapGestureRecognizer NumberOfTapsRequired="1" Tapped="OnItemSelected"></TapGestureRecognizer>
</StackLayout.GestureRecognizers>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</RefreshView>
</StackLayout>
</ContentPage>
ItemsViewModel.cs
namespace TabTest.ViewModels
{
public class ItemsViewModel : BaseViewModel
{
public ObservableCollection<Item> Items { get; set; }
public Command LoadItemsCommand { get; set; }
private string testVal;
public string TestVal // I want the value of this variable in that Label
{
get
{
return testVal;
}
set
{
testVal = value;
}
}
public ItemsViewModel()
{
Title = "Browse";
TestVal = "Value123";
Items = new ObservableCollection<Item>();
LoadItemsCommand = new Command(async () => await ExecuteLoadItemsCommand());
MessagingCenter.Subscribe<NewItemPage, Item>(this, "AddItem", async (obj, item) =>
{
var newItem = item as Item;
Items.Add(newItem);
await DataStore.AddItemAsync(newItem);
});
}
async Task ExecuteLoadItemsCommand()
{
IsBusy = true;
try
{
Items.Clear();
var items = await DataStore.GetItemsAsync(true);
foreach (var item in items)
{
Items.Add(item);
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
finally
{
IsBusy = false;
}
}
}
}
I ended up using a dynamic resource in Xaml and used code behind to modify the resource when it needed to change.
Xaml:
<Label x:Name="TestV" Text="{DynamicResource TestValue}" />
Code Behind:
Application.Current.Resources["TestValue"] = NewValue;
App.xaml:
<x:String x:Key="TestValue">Value123</x:String>

How to apply Interaction.Behaviors command event for listview in Xamarin.Forms?

I am trying to convert ListView ItemSelected or ItemTapped event into command for binding command using Xamarin.Behaviour package with Xamarin.Forms.
It works fine with Button but when I am trying the same with ListView, It gives an error: "systemReflection.TargetInvocationExaptopn".
Here is the code for my ViewModel & Xaml page
MainPage.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:b="clr-namespace:Xamarin.Behaviors;assembly=Xamarin.Behaviors"
x:Class="Xamarin.Behaviors.Demo.Views.MainView"
x:Name="MainPage">
<StackLayout>
<Entry Placeholder="Enter Firstname" >
<b:Interaction.Behaviors>
<b:BehaviorCollection>
<b:TextChangedBehavior Text="{Binding FirstName, Mode=TwoWay}" />
<b:EventToCommand EventName="Unfocused" Command="{Binding UnfocusedCommand}" CommandParameter="FirstName" />
</b:BehaviorCollection>
</b:Interaction.Behaviors>
</Entry>
<Entry Placeholder="Enter Lastname" >
<b:Interaction.Behaviors>
<b:BehaviorCollection>
<b:TextChangedBehavior Text="{Binding LastName, Mode=TwoWay}" />
<b:EventToCommand BindingContext="{b:RelativeContext MainPage}"
EventName="Unfocused"
Command="{Binding UnfocusedCommand}"
CommandParameter="LastName" />
</b:BehaviorCollection>
</b:Interaction.Behaviors>
</Entry>
<Button Text="Ok" Command="{Binding TestCommand}" />
<Label Text="{Binding Message}" />
<Label Text="{Binding WelcomeMessage}" />
<ListView ItemsSource="{Binding Items}"
IsGroupingEnabled="false"
RowHeight="60" >
<b:Interaction.Behaviors>
<b:BehaviorCollection>
<b:EventToCommand CommandNameContext="{b:RelativeContext MainPage}"
EventName="ItemSelected"
CommandName="NickSelectedCommand"
CommandParameter="{Binding NickName}" />
</b:BehaviorCollection>
</b:Interaction.Behaviors>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal" Padding="10" >
<StackLayout Orientation="Vertical" HorizontalOptions="FillAndExpand" Spacing="-50" >
<Button Text="{Binding NickName}">
<b:Interaction.Behaviors>
<b:BehaviorCollection>
<b:EventToCommand CommandNameContext="{b:RelativeContext MainPage}"
EventName="Clicked"
CommandName="NickSelectedCommand"
CommandParameter="{Binding NickName}" />
</b:BehaviorCollection>
</b:Interaction.Behaviors>
</Button>
</StackLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
MainViewModel.cs
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Xamarin.Forms;
namespace Xamarin.Behaviors.Demo.ViewModels
{
public class MainViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string firstName = "FirstName";
private string lastName = "LastName";
private Command testCommand;
private Command<object> unfocusedCommand;
private string message;
private string welcomeMessage;
private Command<string> nickSelectedCommand;
public MainViewModel()
{
this.Items = new ObservableCollection<Item>() { new Item() { NickName = "corcav" }, new Item() { NickName = "foo99" }, new Item() { NickName = "bar76" } };
}
public string FirstName
{
get { return this.firstName; }
set {
if (value != this.firstName)
{
this.firstName = value;
this.RaisePropertyChanged();
this.TestCommand.ChangeCanExecute();
}
}
}
public string LastName
{
get { return this.lastName; }
set {
if (value != this.lastName)
{
this.lastName = value;
this.RaisePropertyChanged();
this.TestCommand.ChangeCanExecute();
}
}
}
public string Message
{
get { return this.message; }
private set {
if (value != this.message) {
this.message = value;
this.RaisePropertyChanged();
}
}
}
public string WelcomeMessage
{
get { return this.welcomeMessage; }
set {
if (value != this.welcomeMessage) {
this.welcomeMessage = value;
this.RaisePropertyChanged();
}
}
}
public Command TestCommand
{
get {
return this.testCommand ?? (this.testCommand = new Command(
() =>
{
this.WelcomeMessage = string.Format("Hello {0} {1}", this.FirstName, this.LastName);
},
() =>
{
// CanExecute delegate
return !string.IsNullOrEmpty(this.FirstName) && !string.IsNullOrEmpty(this.LastName);
}));
}
}
public Command<object> UnfocusedCommand
{
get {
return this.unfocusedCommand ?? (this.unfocusedCommand = new Command<object>(
(param) =>
{
this.Message = string.Format("Unfocused raised with param {0}", param);
},
(param) =>
{
// CanExecute delegate
return true;
}));
}
}
public Command<string> NickSelectedCommand
{
get {
return this.nickSelectedCommand ?? (this.nickSelectedCommand = new Command<string>(
(param) =>
{
this.Message = string.Format("Item {0} selected", param);
},
(param) =>
{
// CanExecute delegate
return true;
}));
}
}
public ObservableCollection<Item> Items { get; private set; }
protected virtual void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}

Dependency Properties and Data Context in Silverlight 3

I am working with Silverlight 3 beta, and am having an issue. I have a page that has a user control that I worte on it. The user control has a dependency property on it. If the user control does not define a data context (hence using the parent's data context), all works well. But if the user control has its own data context, the dependency property's OnPropertyChanged method never gets called.
Here is a sample:
My Main Page:
<UserControl x:Class="TestDepProp.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:app="clr-namespace:TestDepProp"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="400" Height="100">
<Grid x:Name="LayoutRoot" Background="White">
<Border BorderBrush="Blue" BorderThickness="3" CornerRadius="3">
<StackPanel Orientation="Horizontal">
<StackPanel Orientation="Vertical">
<TextBlock Text="Enter text here:" />
<TextBox x:Name="entryBlock" Text="{Binding Data, Mode=TwoWay}"/>
<Button Content="Go!" Click="Button_Click" />
<TextBlock Text="{Binding Data}" />
</StackPanel>
<Border BorderBrush="Blue" BorderThickness="3" CornerRadius="3" Margin="5">
<app:TestControl PropOnControl="{Binding Data}" />
</Border>
</StackPanel>
</Border>
</Grid>
</UserControl>
Main Page code:
using System.Windows;
using System.Windows.Controls;
namespace TestDepProp
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
MainPageData data = new MainPageData();
this.DataContext = data;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
int i = 1;
i++;
}
}
}
Main Page's data context:
using System.ComponentModel;
namespace TestDepProp
{
public class MainPageData:INotifyPropertyChanged
{
string _data;
public string Data
{
get
{
return _data;
}
set
{
_data = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("Data"));
}
}
public MainPageData()
{
Data = "Initial Value";
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
}
Control XAML:
<UserControl x:Class="TestDepProp.TestControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:app="clr-namespace:TestDepProp"
>
<Grid x:Name="LayoutRoot" Background="White">
<StackPanel Orientation="Vertical" Margin="10" >
<TextBlock Text="This should change:" />
<TextBlock x:Name="ControlValue" Text="Not Set" />
</StackPanel>
</Grid>
</UserControl>
Contol code:
using System.Windows;
using System.Windows.Controls;
namespace TestDepProp
{
public partial class TestControl : UserControl
{
public TestControl()
{
InitializeComponent();
// Comment out next line for DP to work
DataContext = new MyDataContext();
}
#region PropOnControl Dependency Property
public string PropOnControl
{
get { return (string)GetValue(PropOnControlProperty); }
set { SetValue(PropOnControlProperty, value); }
}
public static readonly DependencyProperty PropOnControlProperty =
DependencyProperty.Register("PropOnControl", typeof(string), typeof(TestControl), new PropertyMetadata(OnPropOnControlPropertyChanged));
private static void OnPropOnControlPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TestControl _TestControl = d as TestControl;
if (_TestControl != null)
{
_TestControl.ControlValue.Text = e.NewValue.ToString();
}
}
#endregion PropOnControl Dependency Property
}
}
Control's data context:
using System.ComponentModel;
namespace TestDepProp
{
public class MyDataContext : INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
}
To try it out, type something in the text box, and hit the Go button. Comment out the data context in the controls code to see that it starts to work.
Hope someone has an idea as to what is going on.
The user control's datacontext does not have a Data property.
Because it doesn't have a data property the databinding returns null which is already the default value so the property change never fires.

Resources