I have a Pivot Control and three PivotItems added in Blend. Each PivotItem has a ViewModel as it's DataContext. Each ViewModel has a string property Title. I want to edit the Header Template, create a TextBlock and bind it's Text property to Title, but for some reason it doesn't work. I am sure it's because the DataContext of the Header Template is null, but I thought it would be inherited from the specific PivotItem. This is the xaml code for the Header Template.
<DataTemplate x:Key="HeaderTemplate">
<Grid Height="47" Width="354">
<TextBlock Margin="8,0,64,8" TextWrapping="Wrap" Text="{Binding Title}" d:LayoutOverrides="Width" FontSize="24" Foreground="Red"/>
</Grid>
</DataTemplate>
<controls:Pivot TitleTemplate="{StaticResource TitleTemplate}" HeaderTemplate="{StaticResource HeaderTemplate}">
<!--Pivot item one-->
<controls:PivotItem DataContext="{Binding Home, Mode=OneWay}">
<Grid>
<ListBox x:Name="listBox" ItemsPanel="{StaticResource HomeItemsPanel}" ItemTemplate="{StaticResource HomeItemTemplate}" ItemsSource="{Binding Tiles}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<GalaSoft_MvvmLight_Command:EventToCommand Command="{Binding OnSelectionChanged}" CommandParameter="{Binding SelectedIndex, ElementName=listBox}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
</Grid>
</controls:PivotItem>
<controls:PivotItem Margin="0,8,0,1">
<Grid/>
</controls:PivotItem>
<!--Pivot item two-->
<controls:PivotItem DataContext="{Binding More, Mode=OneWay}">
<Grid/>
</controls:PivotItem>
</controls:Pivot>
I want to create a more complex Header Template but for now I am testing if I can bind it, before proceeding on with something more than just a TextBlock. I know that I could use a List of ViewModels for the PivotControl.ItemSource, but then I would have to use a DataTemplateSelector and so on, and I don't want that.
So I guess my question is this, how can I make the HeaderTemplate inherit the DataContext of the according PivotItem?
Thank you.
That won't work. You should write HeaderTemplate the following way:
<controls:Pivot.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</controls:Pivot.HeaderTemplate>
Textblock will receive the binding from the PivotItem's Header property. For each PivotItem, you bind its Header:
<controls:PivotItem Header="{Binding Title}">
<Grid/>
</controls:PivotItem>
The only problem remains in binding each pivot item to its own data context. You can do that manually for each item with the following code:
<controls:PivotItem DataContext="{Binding Item1}" Header="{Binding Title}">
<Grid/>
</controls:PivotItem>
<controls:PivotItem DataContext="{Binding Item2}" Header="{Binding Title}">
<Grid/>
</controls:PivotItem>
Related
I'm creating a custom control for an entry that can be validated.
I did this by creating a ContentView that has a Grid as it's child that contains the entry, error label, etc.
I'd like this to be flexible when it comes to validation, so ideally it would be nice to expose the Entry's MultiValidationBehavior's Children property, or set that property as my control's content property.
As it stands now, I haven't figured out a way to add behaviors to my custom control.
Is this possible?
<ContentView x:Class="MPK.UI.Views.Components.FormEntry"
x:Name="FormEntryControl"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
xmlns:yummy="clr-namespace:Xamarin.Forms.PancakeView;assembly=Xamarin.Forms.PancakeView"
xmlns:converters="clr-namespace:MPK.UI.Converters;assembly=MPK.UI">
<ContentView.Resources>
<ResourceDictionary>
<converters:IsValidToEntryBorderConverter x:Key="IsValidToEntryBorderConverter"/>
<converters:ErrorsToLabelTextConverter x:Key="ErrorsToLabelTextConverter"/>
<xct:InvertedBoolConverter x:Key="InvertedBoolConverter" />
</ResourceDictionary>
</ContentView.Resources>
<Grid>
<yummy:PancakeView CornerRadius="10"
HeightRequest="50"
HorizontalOptions="FillAndExpand"
BackgroundColor="{StaticResource EntryBackgroundColor}"
Padding="16,0,16,0">
<yummy:PancakeView.Behaviors>
<xct:AnimationBehavior AnimateCommand="{Binding Source={x:Reference FormEntryControl}, Path=ShakeCommand}">
<xct:AnimationBehavior.AnimationType>
<xct:ShakeAnimation />
</xct:AnimationBehavior.AnimationType>
</xct:AnimationBehavior>
</yummy:PancakeView.Behaviors>
<yummy:PancakeView.Border>
<yummy:Border
Color="{Binding IsValid, Source={x:Reference MultiValidationBehavior}, Converter={StaticResource IsValidToEntryBorderConverter}}"
Thickness="1" />
</yummy:PancakeView.Border>
<Entry x:Name="Entry"
Text="{Binding Text, Source={x:Reference FormEntryControl}}"
Placeholder="{Binding Placeholder, Source={x:Reference FormEntryControl}}"
ReturnType="{Binding ReturnType, Source={x:Reference FormEntryControl}}"
ReturnCommand="{Binding ReturnCommand, Source={x:Reference FormEntryControl}}"
PlaceholderColor="{StaticResource EntryPlaceholderTextColor}"
BackgroundColor="Transparent"
IsPassword="{Binding IsPassword, Source={x:Reference FormEntryControl}}"
ClearButtonVisibility="{Binding ClearButtonVisibility, Source={x:Reference FormEntryControl}}">
<Entry.Effects>
<xct:RemoveBorderEffect />
</Entry.Effects>
<Entry.Behaviors>
<xct:MultiValidationBehavior x:Name="MultiValidationBehavior"
IsValid="{Binding IsValid, Source={x:Reference FormEntryControl}, Mode=TwoWay}"
Children="{Binding ValidationBehaviors, Source={x:Reference FormEntryControl}}"/>
<!-- Binding children doesn't work here -->
</Entry.Behaviors>
</Entry>
</yummy:PancakeView>
<xct:Expander Margin="8,4,0,0"
AnimationLength="100"
IsExpanded="{Binding IsValid, Source={x:Reference FormEntryControl}, Mode=OneWay, Converter={StaticResource InvertedBoolConverter}}">
<Label Text="{Binding Errors, Source={x:Reference MultiValidationBehavior}, Converter={StaticResource ErrorsToLabelTextConverter}}"
TextColor="{StaticResource ErrorColor}" />
</xct:Expander>
</Grid>
</ContentView>
The solution was much simpler than I expected.
In my control's code behind I needed to add a property that points to the multivalidationbehavior's children property.
public IList<ValidationBehavior> ValidationBehaviors => TheMultiValidationBehavior.Children;
Using my custom control looks something like this:
<components:FormEntry Placeholder="Name">
<components:FormEntry.ValidationBehaviors>
<xct:TextValidationBehavior MinimumLength="1" xct:MultiValidationBehavior.Error="Min: 1"/>
</components:FormEntry.ValidationBehaviors>
</components:FormEntry>
I want to bind to a variable from inside a CustomControl referenced in a ListView DataTemplate
This is the situation:
I have a custom control containing a ListView with many DataTemplates to display the various ViewCell (it's basically a set of dynamic fields that must be displayed according to their "type").
<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:s4gvForms="clr-namespace:S4GVMobile.Forms;assembly=S4GVMobile"
xmlns:s4gvControls="clr-namespace:S4GVMobile.Controls;assembly=S4GVMobile"
x:Class="S4GVMobile.Controls.AttributesList"
x:Name="S4GVAttributesListControl"
>
<ContentView.Resources>
<ResourceDictionary>
<!-- MANY DATA TEMPLATES HERE, ONLY ONE LEFT FOR REFERENCE -->
<DataTemplate x:Key="s4gvAttributePictureTemplate">
<ViewCell x:Name="s4gvAttributePictureViewCell">
<ViewCell.View>
<StackLayout Orientation="Vertical">
<Label Text="{Binding Attribute.Key}" />
<s4gvControls:AttributePicture ParentID="{Binding Source={x:Reference s4gvAttributePictureViewCell}, Path=Parent.BindingContext.EntityID}" AttributeValue="{Binding .}" />
</StackLayout>
</ViewCell.View>
</ViewCell>
</DataTemplate>
<!-- ... -->
<!-- AGAIN, LONG LIST IN THE SELECTOR -->
<s4gvForms:AttributeDataTemplateSelector x:Key="s4gvAttributeDataTemplateSelector"
AttributePictureTemplate="{StaticResource s4gvAttributePictureTemplate}"
/>
</ResourceDictionary>
</ContentView.Resources>
<StackLayout>
<ListView x:Name="s4gvAttributes" HasUnevenRows="True" ItemTemplate="{StaticResource s4gvAttributeDataTemplateSelector}" ItemsSource="{Binding AttributeValues}" />
</StackLayout>
I'm passing to this custom control both the list of items (required by the ListView) and an additional EntityID.
<s4gv:AttributesList x:Name="s4gvSerialAttributes" AttributeValues="{Binding Serial.AttributeValues}" EntityID="{Binding Serial.SerialID}" VerticalOptions="FillAndExpand"/>
The custom control has the required bindable properties (AttibuteValues and EntityID) and relies on a dedicated ViewModel
I need to retrieve the EntityID from inside the DataTemplate (and put it in the ParentID property); this value is in the custom control's ViewModel but it is "outside" the ListView's DataTemplates. I've tried using the Parent.BindingContext (it works on Command and CommandParameter) but it seems that it can only go up to the ListView's context and not higher though I need to move one further step up to the CustomControl itself.
<s4gvControls:AttributePicture ParentID="{Binding Source={x:Reference s4gvAttributePictureViewCell}, Path=Parent.BindingContext.EntityID}" AttributeValue="{Binding .}" />
Any idea? I'm happy to restructure the whole thing if required.
Starting with the stock Databound application, I replace the xaml on the MainPage
<StackPanel Margin="0,0,0,17" Width="432">
<TextBlock Text="{Binding LineOne}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}"/>
<TextBlock Text="{Binding LineTwo}" TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}"/>
</StackPanel>
with this:
<StackPanel Margin="0,0,0,17" Width="432">
<TextBlock Name="ItemName" Margin="10,10,0,0" Text="{Binding LineOne}" TextWrapping="Wrap" Style="{StaticResource PhoneTextSubtleStyle}" Grid.Column="0" />
<Button Grid.Column="1" Click="Button_Click" BorderThickness="0" Height="40" HorizontalAlignment="Center">
</StackPanel>
In Button_Click(), I’d like to remove that item from Items. I know the syntax would be something like App.ViewModel.Items.Remove(something)
but I’m missing what that something is. How can I ensure the correct item is removed based on the value of LineOne that is displayed?
Thanks for looking.
There are a few ways to go about doing this. The best way is with an ICommand. But you also need to add a CommandButton class to hold the reference and the parameter.
If you want a quick workaround though, then the sender object in the button click event should be the button you clicked, and its DataContext property should be the list item. A nasty hack but a lot less work than going down the ICommand path if you are just messing around learning the tools.
private void Button_Click(object sender, RoutedEventArgs e) {
App.ViewModel.Items.Remove((ItemViewModel)((Button)sender).DataContext);
}
I can use Interaction.Triggers to catch the textchanged event on a textbox like so:
<TextBox Text="{Binding Title}" Style="{StaticResource GridEditStyle}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged">
<cmd:EventToCommand Command="{Binding TextChanged}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
However when I use it in a datatemplate for a listview celltemplate as follows:
<ListView ItemsSource="{Binding LangaugeCollection}" SelectedItem="{Binding SelectedLangauge}" BorderThickness="0" FontFamily="Calibri" FontSize="11">
<ListView.View>
<GridView>
<GridViewColumn Width="200">
<GridViewColumn.CellTemplate>
<DataTemplate >
<Grid>
<TextBlock Text="{Binding Title}" Style="{StaticResource GridBlockStyle}">
</TextBlock>
<TextBox Text="{Binding Title}" Style="{StaticResource GridEditStyle}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged">
<cmd:EventToCommand Command="{Binding TextChanged}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
the event will not trigger.
Does anyone know why this does not trigger and how to fix it?
When you are in a DataTemplate, the DataContext might not be what you expect. Typically the DataContext in a DataTemplate is set to the item that the DataTemplate represents. If your TextChanged command is on the "main viewmodel" instead of the data item, you need to be more precise in the way that you specify the data binding, for example:
Command="{Binding Source={StaticResource Locator}, Path=Main.TextChanged}"
You can see the issue when you run the code in debug mode (F5) in Studio and observe the Output window. A Data Error will be shown if the DataContext is incorrectly set.
Cheers,
Laurent
It seems something handles the event before the TextBox. Maybe you could listen to Title property (collection) changed inside you ViewModel, because anyway you are calling TextChanged on ViewModel inside the trigger, I suppose.
Btw I think you're missing TwoWay mode in your binding expression.
Hope this helps.
First let me say I'm new to Silverlight. But I have most of the "basic" Silverlight stuff figured out. I'm using Silverlight 3 at the moment.
In a nutshell, I am not seeing my IValueConverter called inside a UserControl. But as with many things, it's not quite that simple. The UserControl is in a DataGrid cell, in a column whose DataColumnTemplate is generated at runtime by XAML.
Here's my DataTemplate for the Column:
StringBuilder CellTemp = new StringBuilder();
CellTemp.Append("<DataTemplate ");
CellTemp.Append("xmlns:aa='clr-namespace:InvTech.AA.Silverlight.UI;assembly=AASilverlight' ");
CellTemp.Append("xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' ");
CellTemp.Append("xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml' ");
CellTemp.Append(">");
CellTemp.AppendFormat("<aa:ProductAssetView DataContext='{{Binding Products[{0}]}}' />", index);
CellTemp.Append("</DataTemplate>");
return CellTemp.ToString();
So the cell's contents are getting bound to my UserControl. This works; I just can't get my IValueConverter called to format the contents of the UserControl the way I want.
The operative parts of the UserControl XAML:
(declare prefix)
xmlns:aaConv="clr-namespace:InvTech.AA.Silverlight.Core;assembly=AA.Core"
(bound controls inside Grid layout)
<TextBox x:Name="txtSAA" Grid.Row="0" Grid.Column="0" Text="{Binding SAA, Converter={StaticResource PercentConverter}, Mode=TwoWay}" Width="35" FontSize="9"/>
<TextBox x:Name="txtOVR" Grid.Row="0" Grid.Column="1" Text="{Binding Overlay, Converter={StaticResource PercentConverter}, Mode=TwoWay}" Width="35" FontSize="9" />
<TextBox x:Name="txtTAA" Grid.Row="0" Grid.Column="2" Text="{Binding TAA, Converter={StaticResource PercentConverter}, Mode=TwoWay}" Width="35" FontSize="9" />
<TextBlock x:Name="tbkCurrent" Grid.Row="0" Grid.Column="3" Text="TODO" Width="35" FontSize="9" />
<Grid.Resources>
<aaConv:PercentValueConverter x:Key="PercentConverter" />
</Grid.Resources>
Is there something obviously wrong here? Is the dynamic XAML a factor? I feel like this should be trivial compared to the dynamic XAML template...
Thanks
Finally figured this out. By moving the Resource declaration to <UserControl.Resources> and putting that tag before the content, my IValueConverters were exercised.