DataTriggerBehavior Doesn't Work With Enum? - xaml

I'm trying to use a DataTriggerBehavior from the Behaviors SDK. But it doesn't seem to work with enums... or else I'm doing something wrong.
You can assume that the DataContext for these examples is something like this (INotifyPropertyChanged is implemented, but I'm not going to show it here):
public class MyDataClass
{
public MyEnum ItemCommand { get; set; }
public string ItemCommandString { get; set; }
}
public enum MyEnum
{
EnumValue1
}
_Button.DataContext = new MyDataClass() { ItemCommand = MyEnum.EnumValue1,
ItemCommandString = "EnumValue1" };
Here is the code that doesn't work (trying to specify an enum value and check against the ItemCommand enum property):
<ToggleButton x:Name="_Button">
<Interactivity:Interaction.Behaviors>
<Core:DataTriggerBehavior Binding="{Binding ItemCommand}"
Value="EnumValue1">
<Core:ChangePropertyAction PropertyName="Command"
TargetObject="{Binding ElementName=_Button}"
Value="{x:Null}">
</Core:ChangePropertyAction>
</Core:DataTriggerBehavior>
</Interactivity:Interaction.Behaviors>
</ToggleButton>
and this code (checking against an enum resource) also does not work:
<UserControl.Resources>
<local:MyEnum x:Key="_MyEnumValue">EnumValue1</local:MyEnum>
</UserControl.Resources>
<ToggleButton x:Name="_Button">
<Interactivity:Interaction.Behaviors>
<Core:DataTriggerBehavior Binding="{Binding ItemCommand}"
Value="{StaticResource _MyEnumValue}">
<Core:ChangePropertyAction PropertyName="Command"
TargetObject="{Binding ElementName=_Button}"
Value="{x:Null}">
</Core:ChangePropertyAction>
</Core:DataTriggerBehavior>
</Interactivity:Interaction.Behaviors>
</ToggleButton>
whereas this code (checking against a string) does work:
<ToggleButton x:Name="_Button">
<Interactivity:Interaction.Behaviors>
<Core:DataTriggerBehavior Binding="{Binding ItemCommandString}"
Value="EnumValue1">
<Core:ChangePropertyAction PropertyName="Command"
TargetObject="{Binding ElementName=_Button}"
Value="{x:Null}">
</Core:ChangePropertyAction>
</Core:DataTriggerBehavior>
</Interactivity:Interaction.Behaviors>
</ToggleButton>
What is the correct way to specify the enum value in the DataTriggerBehavior Value property so that this will work?

you can write a Converter:
public class MyEnumConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
MyEnum myEnumValue = (MyEnum)value;
return myEnumValue.ToString();
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
And use it in XAML:
<ToggleButton x:Name="_Button">
<Interactivity:Interaction.Behaviors>
<Core:DataTriggerBehavior Binding="{Binding ItemCommand, Converter={StaticResource MyEnumConverter}}"
Value="EnumValue1">
<Core:ChangePropertyAction PropertyName="Command"
TargetObject="{Binding ElementName=_Button}"
Value="{x:Null}">
</Core:ChangePropertyAction>
</Core:DataTriggerBehavior>
</Interactivity:Interaction.Behaviors>
</ToggleButton>
Or bind direct to the string as in your sample. Unfortunately DataTriggerBehavior in WinRT is worse that DataTrigger in Windows Phone 8

I was investigating this issue and narrowed the problem down to TypeConverterHelper class.
TypeConverterHelper source
Apparently it doesn’t account for enum types and falls back to some logic which recreates the xaml string for the enum. Parses it as ContentControl and passes back its content. Unfortunately during this step it loses the enum type information and subsequent type casting is not valid.
If you are working with sources and not just NuGet package you can fix it yourself. Just add another overload of Convert method to TypeConverterHelper:
public static Object Convert(string value, Type destinationType)
{
var typeInfo = destinationType.GetTypeInfo();
if (typeInfo.IsEnum)
return Enum.Parse(destinationType, value);
return Convert(value, destinationType.FullName);
}
And of course change the call in DataTriggerBehavior Compare method
from:
rightOperand = TypeConverterHelper.Convert(rightOperand.ToString(), leftOperand.GetType().FullName);
to:
rightOperand = TypeConverterHelper.Convert(rightOperand.ToString(), leftOperand.GetType());

Related

Xamarin Forms conditonal formatting based on data with DataTrigger

I am developing a chat application in Xamarin Forms and I am trying to add conditional formatting depending on whether it is an incoming or outgoing message.
This is my XAML:
<Frame
Margin="1"
Padding="0"
x:Name="FrameRef"
x:DataType="model:ChatMessage">
<Frame
CornerRadius="10"
Padding="7"
BackgroundColor="LightBlue"
HasShadow="false"
Margin="10,10,80,0">
<Frame.Triggers>
<DataTrigger
TargetType="Frame"
Binding="{Binding Source={x:Reference FrameRef}, Path=x:DataType.From}" Value="+1456456456">
<Setter Property="BackgroundColor" Value="Yellow"/>
</DataTrigger>
</Frame.Triggers>
When I use Path="Margin" and Value="1" it works.
I am now trying to make it work with the Path being x:DataType="model:ChatMessage" and checking the 'from'-field (indicating if the message was incoming or outgoing).
I'm not sure the Data Trigger is quite right for this application, since you're really depending on a data type and not really the content per se of another field. From the documentation:
The DataTrigger class is suitable for checking values on other controls, as well as any property on the control to which it has been added.
What you probably want instead is a Value Converter that handles locating a StaticResource and applying a style for you based on the message type. Full Microsoft Documentation here.
On your XAML element, you'd do something like this:
<Frame Style="{Binding foo, Converter={StaticResource FooToStyleConverter}}"/>
Your converter would work something like this:
public class FooToStyleConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var someValue = (DataTye)value; // Convert 'object' to whatever type you are expecting
// evaluate the converted value
if (someValue.From != null && someValue.From == Enum.SomeoneElse)
return (Style)App.Current.Resources["StyleReceived"]; // return the desired style indicating the message is from someone else
return (Style)App.Current.Resources["StyleSent"]; // return a style indicating the message is from the sender
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
// Usually unused, but inverse the above logic if needed
throw new NotImplementedException();
}
}
Lastly, set up your converter as a Static Resource in App.xaml (or as a local resource on the page) so your page can properly reference it
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DataBindingDemos">
<ContentPage.Resources>
<ResourceDictionary>
<local:FooToStyleConverter x:Key="FooToStyleConverter" />
....

Type not found on XAML namespace

I am trying to access to method class from XAML file.
My class is on folder: project.Utils.
Adding on xaml Content Page:
xmlns:local="project.Utils"
I try to use myConverterMethod class inside Utils folder and use it as:
Converter={StaticResource myConverterMethod}
but error Type myConverterMethod not found in xmlns project.Utils.
Where is my fault?
You can use
xmlns:local="clr-namespace:project.Utils;assembly=project"
It is not possible to refer to a Method within a specific class but to a IValueConverter.
In order to achieve what you want, you need to define a class that implements IValueConverter:
public class IntToBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (int)value != 0;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return (bool)value ? 1 : 0;
}
}
Define the created converter in accessible scope: Page/View or Application. By scope I mean resources:
<ContentPage.Resources>
<ResourceDictionary>
<local:IntToBoolConverter x:Key="intToBool" />
</ResourceDictionary>
</ContentPage.Resources>
and finally consume the converter in the next way:
<Button Text="Search"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
IsEnabled="{Binding Source={x:Reference entry1},
Path=Text.Length,
Converter={StaticResource intToBool}}" />
Xamarin has a very nice documentation that will answer all your questions and it usually has a good code samples.

MDL2 Info Symbol

Microsoft uses a specific symbol for informationial purposes it is a circle with the letter i inside Image of the Symbol. I looked at every resource about the Segoe MDL2 Assets Font but did not find that symbol. Does anyone know if this symbol is part of the font or is it just another image?
The symbol code point is E946.
The following WPF code snippet creates an IEnumerable<int> that contains all symbol code points in Segoe MDL2 Assets.
var typeface = new Typeface(
new FontFamily("Segoe MDL2 Assets"),
FontStyles.Normal, FontWeights.Normal, FontStretches.Normal);
GlyphTypeface glyphTypeface;
typeface.TryGetGlyphTypeface(out glyphTypeface);
var codePoints = glyphTypeface.CharacterToGlyphMap.Keys.Where(c => c > 0x20);
You can easily visualize this collection by setting DataContext = codePoints and writing an ItemsControls like this:
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock
Margin="2" VerticalAlignment="Center"
Text="{Binding StringFormat={}{0:X4}}"/>
<TextBlock
Margin="2" FontFamily="Segoe MDL2 Assets" FontSize="24"
Text="{Binding Converter={StaticResource CodePointConverter}}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
with this CodePointConverter class:
public class CodePointConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return new string((char)(int)value, 1);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}

How to execute command from GridViewItem Tap event (XAML)

I am trying to follow the MVVM pattern in my Windows 8.1 store app (XAML).
I want to navigate to a new view when a GridViewItem is clicked / tapped in the UI. I wanted to do this without code behind events to promote testability (using MVVM Light).
In order to allow my UI to bind to a view model command I have been looking at the Microsoft Behaviors SDK (XAML) added via Add References -> Windows -> Extensions.
The following code in my view compiles but blows up when I tap the grid view item. Unfortunately it offers little help & just throws an unhandled win32 exception [3476].
Can somebody please help shed some light on the problem?
Namespaces used are;
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:core="using:Microsoft.Xaml.Interactions.Core"
<GridView x:Name="itemGridView"
AutomationProperties.AutomationId="ItemGridView"
AutomationProperties.Name="Grouped Items"
ItemsSource="{Binding Source={StaticResource GroupedSource}}"
IsSwipeEnabled="True"
IsTapEnabled="True">
<GridView.ItemTemplate>
<DataTemplate>
<Grid Margin="0"
Height="230">
<StackPanel Orientation="Vertical"
HorizontalAlignment="Stretch">
<Image Source="{Binding Image}"
Stretch="UniformToFill"
HorizontalAlignment="Center"
VerticalAlignment="Center"
/>
<StackPanel VerticalAlignment="Bottom"
Height="45"
Margin="0,-45,0,0">
<StackPanel.Background>
<SolidColorBrush Color="Black"
Opacity="0.75"
/>
</StackPanel.Background>
<TextBlock FontSize="16"
Margin="2"
Text="{Binding Name}"
TextWrapping="Wrap"
VerticalAlignment="Bottom"
/>
</StackPanel>
</StackPanel>
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Tapped">
<core:InvokeCommandAction Command="{Binding DataContext.SummaryCatagorySelectedCommand, ElementName=LayoutRoot}" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Grid>
</DataTemplate>
</GridView.ItemTemplate>
Edit. As requested, I've added the view model, containing specifically the command I want to fire from my behavior.
public class ViewModel : ViewModelBase
{
public RelayCommand<string> SummaryCatagorySelectedCommand { get; set; }
public ViewModel()
{
//
}
}
The simplest answer is to tell you that you should not use a command in this situation. First, the value of a command is that it both executes and communicates back the inability to execute to the interactive XAML control. For example, the button is disabled when the command is not available.
But since you are using the tapped event of a framework element, you are basically just using the control as if it is a simple method, and not a command at all. Your view model can have both commands and methods, of course. And behaviors can call both commands and methods.
To that end, the best scenario here for your solution is to change your approach from calling a command in your view model. Your difficulties are 1. the command is out of scope of the data template and 2. the command parameter is passed inside an out of scope threading context.
Here's what I would suggest to make your life easier and your app simpler.
Do not attach to the tapped event of the item. But instead attach to the itemclicked event of the gridview. This, of course, means you need to set IsItemClickEnabled to true on your gridview. Then don't call to a command, which is overhead you are not using, but instead call to a method.
This is what the method would look like in your viewmodel:
public async void ClickCommand(object sender, object parameter)
{
var arg = parameter as Windows.UI.Xaml.Controls.ItemClickEventArgs;
var item = arg.ClickedItem as Models.Item;
await new MessageDialog(item.Text).ShowAsync()
}
The name of the method does not matter (I even called it a Command to make the point), but the signature does. The behavior framework is looking for a method with zero parameters or with two object-type parameters. Conveniently, the two parameter version gets the event signature forwarded to it. In this case, that means you can use the ItemClickEventArgs which contains the clicked item. So simple.
Your gridview is simplified, too. Instead of trying to force the scope inside your data context you can simply reference the natural scope of the gridview to the outer viewmodel. It would look something like this:
<GridView Margin="0,140,0,0" Padding="120,0,0,0"
SelectionMode="None" ItemsSource="{Binding Items}"
IsItemClickEnabled="True">
<Interactivity:Interaction.Behaviors>
<Core:EventTriggerBehavior EventName="ItemClick">
<Core:CallMethodAction MethodName="ClickCommand"
TargetObject="{Binding Mode=OneWay}" />
</Core:EventTriggerBehavior>
</Interactivity:Interaction.Behaviors>
It's such a simpler solution, and doesn't violate anything in the MVVM pattern because it still pushes the logic out to your separated and testable viewmodel. It lets you effectively use behaviors as an event-to-command but actually using a simpler event-to-method pattern. Since the behavior doesn't pass the CanExecute value back to the control in the first place, this actually simplifies your viewmodel, too.
If, it turns out what you are wanting to do is reuse an existing command that is already leveraged elsewhere (which sounds like the 1% edge case) you can always create a shell method for this purpose that internally leverages the command for you.
As a warning, the RelayCommand that ships with Windows 8.1 does not properly implement ICommand as it does not first test CanExecute before Execute is invoked. In addition, the CanExecute logic in the typed RelayCommand does not pass the CommandParameter to the handler. None of this really matters, depending on who you are using commands in the first place. It matters to me though.
So, that's your answer. Change to GridView.ItemClicked and change from ViewModel.Command to ViewModel.Method. That makes your life easier, far easier, and makes your XAML more portable should you ever want to reuse your data template.
Best of luck!
When you tap on any item you change SelectedItem, I guess. You can Binding (Mode=TwoWay) SelectedItem and in Set() of property raise needed action.
Or you can use something like this and use as dependency property of your GridView.
public class GridViewItemClickCommand
{
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached("Command", typeof(ICommand),
typeof(GridViewItemClickCommand), new PropertyMetadata
(null, CommandPropertyChanged));
public static void SetCommand(DependencyObject attached, ICommand value)
{
attached.SetValue(CommandProperty, value);
}
public static ICommand GetCommand(DependencyObject attached)
{
return (ICommand)attached.GetValue(CommandProperty);
}
private static void CommandPropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
// Attach click handler
(d as GridView).ItemClick += gridView_ItemClick;
}
private static void gridView_ItemClick(object sender,
ItemClickEventArgs e)
{
// Get GridView
var gridView = (sender as GridView);
// Get command
ICommand command = GetCommand(gridView);
// Execute command
command.Execute(e.ClickedItem);
}
}
If you have any problems, please ask :)
I had the same issued but I solved it in another way. I don't put the behavior insie the datatemplate, I do it in the GridView :
<GridView x:Uid="Flow"
x:Name="PlacesGridView"
Grid.Column="1" Grid.Row="2"
ItemTemplate="{StaticResource YourDataTemplate}"
ItemsSource="{Binding YourSource}" >
<Interactivity:Interaction.Behaviors>
<Behaviors:GoToDestinationOnSelected/>
</Interactivity:Interaction.Behaviors>
</GridView>
This is how GoToDestinationOnSelected looks like:
public class GoToDestinationOnSelected : DependencyObject, IBehavior
{
.....
void GoToDestinationOnGridViewItemSelected_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
object obj = (sender as ListViewBase).SelectedItem;
if (obj == null)
return;
if (obj is YourClass)
{
App.RootFrame.Navigate(typeof(CountryPlacesPage));
return;
((AssociatedObject) as ListViewBase).SelectedIndex = -1;
}
public DependencyObject AssociatedObject
{
get;
private set;
}
public void Attach(DependencyObject associatedObject)
{
AssociatedObject = associatedObject;
(associatedObject as ListViewBase).SelectionChanged += GoToDestinationOnGridViewItemSelected_SelectionChanged;
}
public void Detach()
{
(AssociatedObject as ListViewBase).SelectionChanged -= GoToDestinationOnGridViewItemSelected_SelectionChanged;
}
~GoToDestinationOnSelected()
{
}
In UWP(Window 10 and newer) Mobile App, apply the below code snippet
<Interactivity:Interaction.Behaviors>
<Core:EventTriggerBehavior EventName="ItemClick">
<Core:EventTriggerBehavior.Actions>
<Core:`enter code here`InvokeCommandAction Command="{Binding itemclick}"/>
</Core:EventTriggerBehavior.Actions>
</Core:EventTriggerBehavior>
</Interactivity:Interaction.Behaviors>

chessboard type grid on windows phone 8

Im trying to make a grid similar to the way a chessboard looks like in windows phone 8 but im new to developing for the windows phone and using xaml and am not sure where to begin i would like to update a change the colors of the "squares", most examples ive seen are in wpf and they use UniformGrid which is unavailable in windows phone.
so what ive found so far is
<Grid Margin="29,29.5,23,32.5" Height="500">
<Rectangle Stroke="Black">
<Rectangle.Fill>
<SolidColorBrush Color="{DynamicResource color}"/>
</Rectangle.Fill>
</Rectangle>
.
.
.
</grid>
but is their a way to generate a grid of varing size such as 12x12 or 9x8 if i used the code above then i need to make a rectangle for every square which isnt what im going for.
So im just wondering what the xaml would look like it also seems that i need to use data bindings to update the UI. Is their any way to generate a visual grid and be able to update the contents inside. If anyone would could point me in the right direction that would really helpful.
Create a two-level view model of rows and cells. Bind the rows to an ItemsControl, then in the item template, bind the cells to another ItemsControl. Each cell will have a property to tell if it is even or odd, so you can achieve the checkerboard pattern. You can expose game state through additional properties of the cell to display board position through data binding.
Finally, since the cells will have a fixed size, wrap the whole thing in a Viewbox to fit it to your container.
The view models:
public class BoardViewModel
{
private readonly int _rows;
private readonly int _columns;
public BoardViewModel(int rows, int columns)
{
_rows = rows;
_columns = columns;
}
public IEnumerable<RowViewModel> Rows
{
get
{
return Enumerable
.Range(0, _rows)
.Select(row => new RowViewModel(row, _columns))
.ToList();
}
}
}
public class RowViewModel
{
private readonly int _row;
private readonly int _columns;
public RowViewModel(int row, int columns)
{
_row = row;
_columns = columns;
}
public IEnumerable<CellViewModel> Cells
{
get
{
return Enumerable
.Range(0, _columns)
.Select(column => new CellViewModel(_row, column))
.ToList();
}
}
}
public class CellViewModel
{
private readonly int _row;
private readonly int _column;
public CellViewModel(int row, int column)
{
_row = row;
_column = column;
}
public bool IsEven
{
get { return (_row + _column) % 2 == 0; }
}
}
The value converter:
public class CellColorValueConverter : IValueConverter
{
public object Convert(
object value,
Type targetType,
object parameter,
CultureInfo culture)
{
return Application.Current.Resources[
(bool)value == true
? "EvenCellColor"
: "OddCellColor"];
}
public object ConvertBack(
object value,
Type targetType,
object parameter,
CultureInfo culture)
{
throw new NotImplementedException();
}
}
The XAML:
<Window.Resources>
<local:CellColorValueConverter
x:Key="CellColor" />
</Window.Resources>
<Grid>
<Viewbox>
<ItemsControl
ItemsSource="{Binding Rows}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ItemsControl
ItemsSource="{Binding Cells}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel
Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Rectangle
Width="50"
Height="50"
Fill="{Binding IsEven, Converter={StaticResource CellColor}}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Viewbox>
</Grid>