Binding with BasePage in Xamarin Forms - binding

I am implementing a Xamarin app with FreshMVVM framework and I want to use a BasePage to share some code among Pages.
The problem is that when I need to bind some Properties in the MainPage.xaml I have to specify the Source in this way to make it working: Text="{Binding Title, Source={x:Reference mainPage}}". Otherwise without Source Binding doesn't work.
Ok, I get it but is this the right way? Is there another way to achieve the same result? What about when I have plenty of bindings in a page? For instance, is it possible "setting" the Source at un upper level, because in my opinion setting the same Source for each Binding is very annoying.
BasePage.xaml
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="TestXamarin.BasePage"
x:Name="basePage">
<ContentView>
<StackLayout Orientation="Vertical">
<Label Text="HEADER" FontSize="Large"/>
<Label Text="{Binding Text, Source={x:Reference basePage}}" FontSize="Large"/>
<ContentPresenter BindingContext="{Binding Parent.BindingContext}"
Content="{Binding PageContent, Source={x:Reference basePage}}" />
</StackLayout>
</ContentView>
</ContentPage>
BasePage.xaml.cs
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace TestXamarin
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class BasePage : ContentPage
{
public static readonly BindableProperty TextProperty = BindableProperty.Create(
nameof(Text),
typeof(string),
typeof(BasePage));
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly BindableProperty PageContentProperty = BindableProperty.Create(
nameof(PageContent),
typeof(object),
typeof(BasePage));
public object PageContent
{
get { return GetValue(PageContentProperty); }
set { SetValue(PageContentProperty, value); }
}
public BasePage()
{
InitializeComponent();
}
}
}
MainPage.xaml
<local:BasePage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:TestXamarin"
x:Class="TestXamarin.MainPage"
Text="FROM MAIN PAGE"
x:Name="mainPage">
<local:BasePage.PageContent>
<StackLayout>
<Label Text="Body" FontSize="Large"/>
<Label Text="{Binding Title, Source={x:Reference mainPage}}" FontSize="Large"/>
</StackLayout>
</local:BasePage.PageContent>
</local:BasePage>
MainPage.xaml.cs
public partial class MainPage : BasePage
{
public MainPage()
{
Title = "MAIN PAGE";
InitializeComponent();
}
}

Another way to achieve what you are trying to do would be with control templates.
Here I have defined a template in App.xaml
<ControlTemplate x:Key="ActivityIndicatorTemplate">
<Grid>
<ContentPresenter />
<StackLayout Style="{StaticResource BlockingPanel}"
IsVisible="{TemplateBinding BindingContext.IsBusy}">
<ActivityIndicator Style="{StaticResource ActivityIndicatorStyle}"
IsVisible="{TemplateBinding BindingContext.IsBusy}"
IsRunning="{TemplateBinding BindingContext.IsBusy}" />
</StackLayout>
</Grid>
</ControlTemplate>
Note the content presenter and the TemplateBinding.
I use it on a page like this.
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Test.MyTestPage"
ControlTemplate="{StaticResource ActivityIndicatorTemplate}"
Title="{Binding Title}">
<Grid>
...
</Grid>
</ContentPage>
The page content replaces the content presenter in the template. Looks simpler than a base page.

Related

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 do I bind a Dataset to a Datagrid with an ObjectDataProvider?

i've got another binding problem.
This time I wanted to rebuild the Master-Detail grid shown here:
http://www.codeproject.com/Articles/30905/WPF-DataGrid-Practical-Examples#masterdetail
But I got this Error: The name "AirplaneDataProvider" does not exist in the namespace "clr-namespace:WpfApplicationDataSetTest"
Here's my code
XAML:
<Window x:Class="WpfApplicationDataSetTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplicationDataSetTest"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<ObjectDataProvider x:Key="AirplaneDataProvider" ObjectType="{x:Type local:AirplaneDataProvider}"/>
<ObjectDataProvider x:Key="Airplanes" ObjectInstance="{StaticResource AirplaneDataProvider}" MethodName="GetAirplanes" />
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<DataGrid Name="DGMaster" Grid.Row="0" ItemsSource="{Binding Source={StaticResource Airplanes}}" SelectedValuePath="AirplaneID">
</DataGrid>
</Grid>
c#:
namespace WpfApplicationDataSetTest
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public class AirplaneDataProvider
{
private AirplaneTestDataSetTableAdapters.AirplaneTableAdapter AirTA;
private AirplaneTestDataSet AirTDS;
public AirplaneDataProvider()
{
AirTDS = new AirplaneTestDataSet();
AirTA = new AirplaneTestDataSetTableAdapters.AirplaneTableAdapter();
AirTA.Fill(AirTDS.Airplane);
}
public DataView GetAirplanes()
{
return AirTDS.Airplane.DefaultView;
}
}
}
}
So what am I doing wrong?
Put the AirplaneDataProvider class into separated file.
Now you have it inside MainWindow class. hope that is the problem.

Bind a ListBox or ItemsControl inside a Custom Button Control

Hi I am new to XAML and I am trying to build a custom toggle button control. My control has the following code which does not give any compile or run time error but does not display the data. I kept getting error when I tried to do regular templatebinding, after research I found that I need to use Relative Source. But cant get the list to populate. Can someone guide on what I am doing wrong? Or if there is a better way to do this?
Objective: Build a checkable control which has a list control.
XAML Generic.xml: (this code is inside a togglebutton controltemplate)
<ListBox BorderBrush="Red" BorderThickness="2"
ItemsSource="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=MyRoutes}">
<!--<ItemsControl.ItemsSource>
<Binding Mode="TwoWay" Path="MyRoutes">
<Binding.RelativeSource>
<RelativeSource Mode="TemplatedParent"/>
</Binding.RelativeSource>
</Binding>
</ItemsControl.ItemsSource>-->
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<!--<TextBlock Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Value}"></TextBlock>-->
<TextBlock Text="{Binding Path=Value, RelativeSource={RelativeSource Self}}"></TextBlock>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>
Code Behind PlainToggleButton.cs:
public string[] MyRoutes
{
get { return (string[])GetValue(MyRoutesProperty); }
set { SetValue(MyRoutesProperty, value); }
}
// Using a DependencyProperty as the backing store for MyRoutes. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MyRoutesProperty =
DependencyProperty.Register("MyRoutes", typeof(string[]), typeof(PlainToggleButton), new PropertyMetadata(OnMyRoutesChanged));
private static void OnMyRoutesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
return;
}
Code MainWindow.xaml:
<local:PlainToggleButton Grid.Row="1" MyRoutes="{Binding MyRoutes}">
</local:PlainToggleButton>

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.

Creating a LoginStatusControl in Silverlight

I'm trying to create a login status control in silverlight where I will use multiple ControlTemplates to define conditional content.
So far I have created a LoginStatusControl
public class LoginStatusControl : ContentControl
{
// these are actually Depedency Properties
public ControlTemplate LoggedInTemplate { get; set; }
public ControlTemplate AnonymousTemplate { get; set; }
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
var user = this.DataContext as User;
if (user == null && this.AnonymousTemplate != null)
{
this.Template = this.AnonymousTemplate;
}
else if (this.LoggedInTemplate != null)
{
this.Template = this.LoggedInTemplate;
}
}
}
Then I've defined the templates in a Style.
<Style x:Key="UserStatusStyle" TargetType="local:LoginStatusControl">
<Setter Property="LoggedInTemplate">
<Setter.Value>
<ControlTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="User " />
<TextBlock Text="{Binding FirstName}" />
<TextBlock Text=" " />
<TextBlock Text="{Binding LastName}" />
<TextBlock Text=" is logged in" />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="AnonymousTemplate">
<Setter.Value>
<ControlTemplate>
<TextBlock Text="Please create your profile" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I'm having difficulty getting the conditional templates connected to override the ControlTemplate.
While searching I found this question and tried to use template binding but I couldn't get that to work.
Is there anyway to get this conditional templates to display if the user is logged in or not? Is there another way of solving this problem that I'm missing? I'm hoping to come up with a solution that can update the template dynamically when the DataContext of the control changes.
Well, I ended up going with a ContentContent's Content property and providing conditional DataTemplates.
Here is the Control:
public class LoginStatusControl : ContentControl
{
public DataTemplate LoggedInTemplate
{
get { return (DataTemplate)GetValue(LoggedInTemplateProperty); }
set { SetValue(LoggedInTemplateProperty, value); }
}
// Using a DependencyProperty as the backing store for LoggedInTemplate. This enables animation, styling, binding, etc...
public static readonly DependencyProperty LoggedInTemplateProperty =
DependencyProperty.Register("LoggedInTemplate", typeof(DataTemplate), typeof(LoginStatusControl), new PropertyMetadata(null));
public DataTemplate AnonymousTemplate
{
get { return (DataTemplate)GetValue(AnonymousTemplateProperty); }
set { SetValue(AnonymousTemplateProperty, value); }
}
// Using a DependencyProperty as the backing store for AnonymousTemplate. This enables animation, styling, binding, etc...
public static readonly DependencyProperty AnonymousTemplateProperty =
DependencyProperty.Register("AnonymousTemplate", typeof(DataTemplate), typeof(LoginStatusControl), new PropertyMetadata(null));
public LoginStatusControl()
{
DefaultStyleKey = typeof(LoginStatusControl);
}
public override void OnApplyTemplate()
{
UpdateTemplate();
base.OnApplyTemplate();
}
private void UpdateTemplate()
{
var content = (ContentControl)base.GetTemplateChild("LoginControl");
if (content == null)
return;
var user= this.DataContext as User;
if (user == null && this.AnonymousTemplate != null)
{
content.Content = this.DataContext;
content.ContentTemplate = this.AnonymousTemplate;
}
else if (this.LoggedInTemplate != null)
{
content.Content = this.DataContext;
content.ContentTemplate = this.LoggedInTemplate;
}
}
}
And here is the Default Style.
<Style x:Key="LoginStatusStyle" TargetType="controls:LoginStatusControl">
<Setter Property="LoggedInTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="User: "/>
<TextBlock Text="{Binding FirstName}" FontWeight="Bold" />
<TextBlock Text=" " />
<TextBlock Text="{Binding LastName}" FontWeight="Bold" />
<TextBlock Text=" is logged in" />
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="AnonymousTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="Please create your profile" />
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<ContentControl x:Name="LoginControl" Margin="10,0" VerticalAlignment="Center" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

Resources