Creating a LoginStatusControl in Silverlight - controltemplate

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>

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>

Binding with BasePage in Xamarin Forms

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.

Listbox binding get selected item value

I'm new in Wpf and i face issue to get selected item in listbox
I created a simple xaml with a listbox and a textbox.
I use binding to populate my listbox including trigger (checked or not) that i want ot use later.
Xaml code:
<ListBox x:Name="LstB_Checklist" HorizontalAlignment="Left" Height="190" Margin="39,45,0,0" VerticalAlignment="Top" Width="275" Background="#FF363636" BorderBrush="{x:Null}" FontSize="18" Foreground="White" BorderThickness="2" SelectionChanged="LstB_Checklist_SelectionChanged" SelectedItem="{Binding SelectedProperty,Mode=TwoWay}" >
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Style.Triggers>
<Trigger Property="IsSelected" Value="True" >
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="Background" Value="#FFFFDC00" />
<Setter Property="Foreground" Value="Black" />
</Trigger>
</Style.Triggers>
<Style.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="LightGray"/>
</Style.Resources>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image>
<Image.Style>
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<DataTrigger Binding="{Binding Checked}" Value="false">
</DataTrigger>
<DataTrigger Binding="{Binding Checked}" Value="true">
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<TextBlock Text="{Binding Path=Title, Mode=TwoWay}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBox x:Name="txtb_Selection" HorizontalAlignment="Left" Height="23" Margin="60,264,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120"/>
in the code behind
public MainWindow()
{
InitializeComponent();
List<LstB_Item> items = new List<LstB_Item>();
items.Add(new LstB_Item() { Title = "Items 1", Checked = false });
items.Add(new LstB_Item() { Title = "Items 2", Checked = false });
items.Add(new LstB_Item() { Title = "Items 3", Checked = false });
LstB_Checklist.ItemsSource = items;
}
public class LstB_Item : INotifyPropertyChanged
{
public string Title { get; set; }
private bool _checked;
public bool Checked
{
get { return _checked; }
set { _checked = value; NotifyPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private void LstB_Checklist_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
txtb_Selection.Text = LstB_Checklist.SelectedItem.ToString();
}
The lisbox is populated correctly. My question is: What is the right code to get the value selected item.
Many thanks for the support
I'm afraid the question was not so clear or the answer evident, but i post an answer, it could help:
Possible answer:
var selected = LstB_Checklist.SelectedItem as LstB_Item;
txtb_Selection.Text = selected.Title;

how to add DataTemplate to .Xaml file dynamically

I am using T4 template to generate datatemplate. here is the function which I am using.
public static bool AppendDataTemplate(string filePath, string dataTemplateToBeAppended)
{
try
{
XmlNode newDataTemplateNode = CreateNodeFromXmlString(dataTemplateToBeAppended);
if (newDataTemplateNode != null)
{
if (newDataTemplateNode.Attributes != null)
{
string itemTemplateKey = newDataTemplateNode.Attributes["x:Key"].Value;
XmlElement resourceDictionaryRoot;
XmlDocument existingXmlDocument = GetRootDocumentFromFile(filePath, out resourceDictionaryRoot);
if (resourceDictionaryRoot != null)
{
if (
resourceDictionaryRoot.ChildNodes.Cast<XmlNode>()
.Any(
node =>
node.Attributes != null && node.Attributes["x:Key"].Value == itemTemplateKey))
{
return true;
}
if (resourceDictionaryRoot.OwnerDocument != null)
{
XmlNode importNode = resourceDictionaryRoot.OwnerDocument.ImportNode(
newDataTemplateNode,
true);
resourceDictionaryRoot.AppendChild(importNode);
}
}
else
{
throw new ApplicationException(
"There is some issue while getting xml from the specified file");
}
existingXmlDocument.Save(filePath);
}
}
else
{
throw new ApplicationException("There is some issue while converting specified string to XML");
}
}
catch (Exception ex)
{
throw;
}
return true;
}
private static XmlNode CreateNodeFromXmlString(string xml)
{
var newDataTemplateDocument = new XmlDocument();
newDataTemplateDocument.XmlResolver = null;
newDataTemplateDocument.LoadXml(xml);
return newDataTemplateDocument.DocumentElement;
}
This is working fine but only issue is, I had to add xml namespace along with datatemplate else it throws error, is there anyway We can stop XML validation? see below template, if I pass without xmlns it throws validation error.. any suggestions?
<DataTemplate x:Key="PersonItemTemplate" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid HorizontalAlignment="Left" Width="250" Height="250">
<Border Background="{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}">
<Image Source="{Binding ImageUrl}" Stretch="UniformToFill" />
</Border>
<StackPanel VerticalAlignment="Bottom" Background="{StaticResource ListViewItemOverlayBackgroundThemeBrush}">
<TextBlock Text="{Binding PersonName}" Foreground="{StaticResource ListViewItemOverlayForegroundThemeBrush}" Style="{StaticResource TitleTextStyle}" Height="40" Margin="15,0,15,0" />
<TextBlock Text="{Binding PersonId}" Foreground="{StaticResource ListViewItemOverlaySecondaryForegroundThemeBrush}" Style="{StaticResource CaptionTextStyle}" Margin="15,0,15,10" />
<TextBlock Text="{Binding PersonAddress}" Foreground="{StaticResource ListViewItemOverlaySecondaryForegroundThemeBrush}" Style="{StaticResource CaptionTextStyle}" Margin="15,0,15,10" />
<TextBlock Text="{Binding BirthDate}" Foreground="{StaticResource ListViewItemOverlaySecondaryForegroundThemeBrush}" Style="{StaticResource CaptionTextStyle}" Margin="15,0,15,10" />
</StackPanel>
</Grid>
</DataTemplate>

Image flickering in WP7 ListBox

I'm trying to display a list of images embedded in my asssembly in a ListBox. I can get the images to display using a converter, but rather than loading, then staying still, they constantly reload from the assembly causing them to flicker. I'm using the same Converter to load the icons in various other places around my app but this problem does not occur- it seems to be cause by the lisbox somehow. I've tried removing the VisualStates and switching the CreateOption for the Bitmap image which the converter returns, but I get the same result. I'm fairly sure this didn't happen on WP7.0, only 7.1.
The style is:
<Style x:Key="SnapshotList" TargetType="ListBox">
<Setter Property="Margin" Value="2" />
<Setter Property="BorderThickness" Value="1"/>
<!--<Setter Property="Background" Value="{StaticResource WindowBackgroundBrush}" />
<Setter Property="BorderBrush" Value="{StaticResource PhoneBorderBrush}" />-->
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/>
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
<!--Setter Property="OverridesDefaultStyle" Value="True"/-->
<Setter Property="ItemTemplate" Value="{StaticResource SnapshotTemplate}"/>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<Controls:WrapPanel HorizontalAlignment="Stretch" VerticalAlignment="Top"/>
<!--<WP7:BindingHelper.Binding>
<WP7:RelativeSourceBinding Path="(FrameworkElement.ActualWidth)" TargetProperty="Width"
RelativeMode="FindAncestor"
AncestorType="ScrollContentPresenter" />
</WP7:BindingHelper.Binding>-->
<!--</Controls:WrapPanel>-->
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBox">
<Border Name="Border" Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{StaticResource PhoneBorderBrush}" CornerRadius="2">
<ScrollViewer Margin="0">
<ItemsPresenter/>
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
The listbox in question:
<ListBox x:Name="lstIcon" ItemsSource="{Binding AvailableIcons}" SelectedItem="{Binding Recipe.Icon,Mode=TwoWay}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" SelectionMode="Single"
Style="{StaticResource SnapshotList}">
<ListBox.ItemTemplate>
<DataTemplate>
<Border Background="Transparent" MinWidth="30" MinHeight="30" Margin="3" Padding="3">
<Image Source="{Binding Converter={StaticResource IconConverter}, ConverterParameter=32}" Stretch="None" MinWidth="30" MinHeight="30" />
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
The converter:
public class IconConverter : IValueConverter
{
private static IEnumerable<string> _names;
private static IEnumerable<string> ResourceNames {
get {
if (_names == null) {
_names = WP7Utils.GetResourcePaths(Assembly.GetExecutingAssembly()).Select(p=>System.Convert.ToString(p)).ToList();
}
return _names;
}
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
string baseFilename = (value ?? Shared.Constants.DefaultIcon).ToString().Trim();
string size = (parameter ?? "32").ToString();
try {
BitmapImage img = new BitmapImage();
using (var store = IsolatedStorageFile.GetUserStoreForApplication()) {
string fileName = string.Format("{0}_{1}.png", baseFilename, size);
img = ResourceHelper.GetBitmap("Resources/types/" + fileName, "MyAssembly");
}
return img;
} catch (Exception ex) {
Console.WriteLine(string.Format("Error loading image {0} ({1}px): {2}", baseFilename, size, ex.Message));
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
throw new NotImplementedException();
}
}
This was caused by specifying the MinHeight and MinWidth in the DataTemplate for the ListBox. Removing the attributes fixed the problem.
You should also set the caching on the caching on the image to BitmpaCache to prevent the need for the framework to need to reload/redraw the image.

Resources