How to bind ToggleButtons to Enum values on ItemsControl? - data-binding

I want to create a row of ToggleButtons for Enum values. The buttons must show current value of a property of MyEnumType (by their state) and change the property's value when pressed.
I've found a solution for binding a bunch of separate CheckBoxes to their corresponding enum values (one for each) here, but I'm trying to create the ToggleButtons by ItemsControl (from the Enum type's values) so I wont have to remember to add a ToggleButton every time I add another value to my enum type (and also for shorter XAML code that creates the buttons). The problem is that I can't bind a ConverterParameter. Is there a clean and proper way to do this? Or am I doing everything wrong?
Here is my code now:
<ItemsControl ItemsSource="{Binding Source={local:EnumValues {x:Type local:MyEnumType}}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ToggleButton Content="{Binding Converter={StaticResource MyEnumToNiceStringConverter}}"
IsChecked="{Binding Source=mySourceObject, Path=SelectedMyEnumValue, Converter={StaticResource EnumBooleanConverter}, ConverterParameter={Binding}}">
</ToggleButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The local:EnumValues is a MarkupExtension that returns a list of values from given Enum type.
The EnumBooleanConverter is a value converter from the above link that returns true if the bound enum value is equal to its ConverterParameter and supports ConvertBack from bool to enum value.
SelectedMyEnumValue is the property that the buttons are to reflect and modify.
This is a repeating problem for me (that some property cannot be bound) so if you are about to give me a totally different approach for the ToggleButtons, please also write how to work around the problem of such binding. It doesn't have to be bound forever, I just need to set the value once (without a bunch of Style-and-Triggers kind of code in XAML). Maybe another Markup Extension can do this?
Thanks!

I have found out that my question is a duplicate of this one (I got confused by its title and missed it). Sorry for this. I'm attaching a solution for my specific case based on answers from that post (using ListBox instead of ItemsControl and binding IsChecked to IsSelected). The main trick here is the style for hiding the selected item's blue background.
<ListBox ItemsSource="{Binding Source={local:EnumValues {x:Type local:MyEnumType}}}"
SelectedItem="{Binding Source=mySourceObject, Path=SelectedMyEnumValue}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.Resources>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<ContentPresenter />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<ToggleButton Content="{Binding Converter={StaticResource MyEnumToNiceStringConverter}}"
IsChecked="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ListBoxItem}, Path=IsSelected}">
</ToggleButton>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Anyway, this only solves one of my 2 questions, so if you know how to make binding of such properties as the ConverterParameter possible, please leave an answer. Thanks.

Related

Customize look of selected element

Is there any possibility to customize the look of the active/selected item in the indicator view? Using Data Template Selector or something?
Data template changes every single item.
My goal is to reach this effect:
Try using trigger when color changes of your view at that time increase view's width or you may use view's PropertyChanged event to detect changes in view. I had a similar kind of requirement today and below code worked for me.
<IndicatorView
x:Name="introductionIndicator"
Grid.Row="2"
HorizontalOptions="Center"
IndicatorColor="{StaticResource PrimaryLightGrayColor}"
SelectedIndicatorColor="{StaticResource PrimaryYellowColor}">
<IndicatorView.IndicatorTemplate>
<DataTemplate>
<BoxView
CornerRadius="10"
HeightRequest="6"
WidthRequest="20">
<BoxView.Triggers>
<Trigger TargetType="BoxView" Property="BackgroundColor" Value="{StaticResource PrimaryYellowColor}">
<Setter Property="WidthRequest" Value="30" />
</Trigger>
</BoxView.Triggers>
</BoxView>
</DataTemplate>
</IndicatorView.IndicatorTemplate>
</IndicatorView>
result:

XamarinForm DataTemplate ListView Binding

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.

binding a Custom control in DataTemplate

I've been tackling a problem regarding databinding in XAML for a little while now.
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Pedagouge.Views;assembly=Pedagouge"
x:Class="Pedagouge.Views.StudentGroupView">
<StackLayout>
<ListView x:Name="Students">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<local:PhotoView x:Name="Photo" Persona="{Binding}" />
<StackLayout Orientation="Vertical" HorizontalOptions="StartAndExpand" VerticalOptions="StartAndExpand">
<Label Text="{Binding FullName}" />
<Label Text="{Binding StudentIsAt.Group.Name}" />
</StackLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
I thought I had binding figured, but the problem I'm having clearly shows that I'm not.
Anyways, the problem is that for the custom control PhotoView it seems that the binding context used by {Binding} to the property Persona is the PhotoView's BindingContext, not the DataTemplate's context, as it is for the Labels a couple of rows down.
Had this been WPF, I would have just changed the binding to
Persona="{Binding DataContext,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ListBoxItem}}"
and that would probably have fixed it, but that won't work here (RelativeSource is apparently not defined).
How come? How can I make Persona bound to the DataTemplates context, as is the case with the Labels, instead?
Xamarin.Forms (up to 1.2.x) bindings always use the BindableObject.BindingContext as the context binding.
In the case of templates items, the item context is set to the template element, in this case the ViewCell, and recursively to the ViewCell.View, the StackLayout, your PhotoView, the inner StackLayout and the 2 Labels.
The fact that FullName and StudentIsAt.Group.Name works is a strong hint.
So, when you say
[...] for the custom control PhotoView it seems that the binding context used by
{Binding} to the property Persona is the PhotoView's BindingContext, not the
DataTemplate's context, [...]
it's both true and wrong.
The binding context used by {Binding} is the PhotoView's Bindingcontext, which is also the DataTemplate's context.
If the Binding does not work as you expect, it's probably because PhotoView somehow set the BindingContext to this, and prevent the {Binding} to work as expected, but I can't say without seeing PhotoView's code.

Panorama Binding on WP7, Items collection must be empty before using ItemsSource

i keep getting this error: Items collection must be empty before using ItemsSource, when i attempt to bind data to a Panorama control. below is my xaml snippet.
<controls:Panorama x:Name="panorama">
<controls:PanoramaItem >
<StackPanel>
<TextBlock Text="{Binding Text}"/>
</StackPanel>
</controls:PanoramaItem>
</controls:Panorama>
in my code behind (xaml.cs), i do something like this:
protected override void OnNavigatedTo(NavigationEventArgs e) {
string id = NavigationContext.QueryString["id"];
ObservableCollection<MyObject> list = DataAccessService.get(id);
panorama.ItemsSource = list;
base.OnNavigatedTo(e);
}
note that MyObject has a Text property. any help is appreciated.
after modifying it per the link below, the same exception is still thrown.
<controls:Panorama x:Name="panorama">
<controls:Panorama.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}"/>
</DataTemplate>
</controls:Panorama.HeaderTemplate>
<controls:PanoramaItem >
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Text}"/>
</StackPanel>
</DataTemplate>
</controls:PanoramaItem>
</controls:Panorama>
finally, after continuing further with the user below's help, this is the solution that got rid of the exception.
<controls:Panorama x:Name="panorama">
<controls:Panorama.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}"/>
</DataTemplate>
</controls:Panorama.HeaderTemplate>
<controls:Panorama.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Text}"/>
</StackPanel>
</DataTemplate>
</controls:Panorama.ItemTemplate>
</controls:Panorama>
Your problem is that you're building the Panorama in XAML as though it were static rather than preparing it to be data-bound.
Take a look at this quick tutorial for a Data Binding Panorama Control:
Data Binding Panorama Control WP7 MVVM
Notice the difference in how your XAML is being constructed for the control. Instead of setting the Items collection on the Panorama control, you need to set the HeaderTemplate and ItemTemplate so that the control knows how to render things once data is bound to it.

Bound combobox: text disappearing after sorting the source list of strings

Ive got an ObservableCollection<string> list, which is bound to a combobox. This combobox is in a datatemplate which is inside a 'DataGridTemplateColumn'.
When the datagrid is displayed (with all the rows), the column displaying this combobox works just fine. The user can select the items in the combobox, and when it's selected, the string is bound to the cell. (Just for your info: the datagrid is bound to another ObservableCollection so the cell text gets updated in that list - but i don't think it's relevant to my problem).
This is all good but a problem arises when i go to 'add' another item in the ObservableCollection<string> list that the combo box is bound to, and perform a sort. The text disappears in the 'textbox' part of some of the previously modified comboboxes. If i do not sort the list, (just add a new value) everything is fine.
I think what is happenning is that the binding gets screwed up when i re-sort the list. Because the list has 'changed', the order of the strings in the list are now different, so the binding doesn't know what to display.
How can i get this to work? The previously selected comboboxes's text disappears when i re-sort the ObservableCollection<string> list.
My <DataGridTemplateColumn> containing the combo box is:
<WpfToolkit:DataGridTemplateColumn
Header="Category" Width="1*"
CellTemplate="{StaticResource ComboBoxCellDataTemplate}"
CellEditingTemplate="{StaticResource ComboBoxCellEditingTemplate}"/>
...and the related DataTemplates are:
<DataTemplate x:Key="ComboBoxCellDataTemplate">
<Label x:Name="lblCombo" Content="{Binding Category}" Style="{StaticResource BaseLabelCellStyle}" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Categories, Mode=TwoWay}" Value="Both">
<Setter TargetName="lblCombo" Property="IsEnabled" Value="False" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
<DataTemplate x:Key="ComboBoxCellEditingTemplate">
<!-- min=60, max=600 also, add in a 'specific' scalar value -->
<ComboBox
x:Name="comboBox"
ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Categories, Mode=TwoWay}"
SelectedItem="{Binding Category}" LostFocus="comboBox_LostFocus" IsEditable="True" PreviewKeyDown="comboBox_PreviewKeyDown" MaxDropDownHeight="100" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Enabled}" Value="False">
<Setter TargetName="comboBox" Property="IsEnabled" Value="True" />
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Categories, Mode=TwoWay}" Value="Both">
<Setter TargetName="comboBox" Property="IsEnabled" Value="True" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
Note that the majority of this code is by Samuel Moura at http://sweux.com/blogs/smoura/index.php/tag/datagridcolumn/
Hey I think I have a solution for you. Just add the following line to your Datagrid definition
SelectionUnit="Cell"
I dunno how, it worked for me:) Just give it a try and let me know if it helps.

Resources