x:Static in UWP XAML - c#

An app I'm working on requires a ConverterParameter to be an enum. For this, the regular way to do would be:
{Binding whatever,
Converter={StaticResource converterName},
ConverterParameter={x:Static namespace:Enum.Value}}
However, the UWP platform x: namespace does not seem to have the Static extension.
Does anyone know if there's a solution that does not rely on x:Static for comparing an enum in binding?

This works for me in a UWP:
<Button Command="{Binding CheckWeatherCommand}">
<Button.CommandParameter>
<local:WeatherEnum>Cold</local:WeatherEnum>
<Button.CommandParameter>
</Button>

The most concise way I know of...
public enum WeatherEnum
{
Cold,
Hot
}
Define the enum value in XAML:
<local:WeatherEnum x:Key="WeatherEnumValueCold">Cold</local:WeatherEnum>
And simply use it:
"{Binding whatever, Converter={StaticResource converterName},
ConverterParameter={StaticResource WeatherEnumValueCold}}"

There is no Static Markup Extension on UWP (and WinRT platform too).
One of the possible solutions is to create class with enum values as properties and store instance of this class in the ResourceDictionary.
Example:
public enum Weather
{
Cold,
Hot
}
Here is our class with enum values:
public class WeatherEnumValues
{
public static Weather Cold
{
get
{
return Weather.Cold;
}
}
public static Weather Hot
{
get
{
return Weather.Hot;
}
}
}
In your ResourceDictionary:
<local:WeatherEnumValues x:Key="WeatherEnumValues" />
And here we are:
"{Binding whatever, Converter={StaticResource converterName},
ConverterParameter={Binding Hot, Source={StaticResource WeatherEnumValues}}}" />

This is an answer utilizing resources and without Converters:
View:
<Page
.....
xmlns:local="using:EnumNamespace"
.....
>
<Grid>
<Grid.Resources>
<local:EnumType x:Key="EnumNamedConstantKey">EnumNamedConstant</local:SettingsCats>
</Grid.Resources>
<Button Content="DoSomething" Command="{Binding DoSomethingCommand}" CommandParameter="{StaticResource EnumNamedConstantKey}" />
</Grid>
</Page>
ViewModel
public RelayCommand<EnumType> DoSomethingCommand { get; }
public SomeViewModel()
{
DoSomethingCommand = new RelayCommand<EnumType>(DoSomethingCommandAction);
}
private void DoSomethingCommandAction(EnumType _enumNameConstant)
{
// Logic
.........................
}

Related

Creating a Property of a given Type in XAMLwithout a Converter [duplicate]

An app I'm working on requires a ConverterParameter to be an enum. For this, the regular way to do would be:
{Binding whatever,
Converter={StaticResource converterName},
ConverterParameter={x:Static namespace:Enum.Value}}
However, the UWP platform x: namespace does not seem to have the Static extension.
Does anyone know if there's a solution that does not rely on x:Static for comparing an enum in binding?
This works for me in a UWP:
<Button Command="{Binding CheckWeatherCommand}">
<Button.CommandParameter>
<local:WeatherEnum>Cold</local:WeatherEnum>
<Button.CommandParameter>
</Button>
The most concise way I know of...
public enum WeatherEnum
{
Cold,
Hot
}
Define the enum value in XAML:
<local:WeatherEnum x:Key="WeatherEnumValueCold">Cold</local:WeatherEnum>
And simply use it:
"{Binding whatever, Converter={StaticResource converterName},
ConverterParameter={StaticResource WeatherEnumValueCold}}"
There is no Static Markup Extension on UWP (and WinRT platform too).
One of the possible solutions is to create class with enum values as properties and store instance of this class in the ResourceDictionary.
Example:
public enum Weather
{
Cold,
Hot
}
Here is our class with enum values:
public class WeatherEnumValues
{
public static Weather Cold
{
get
{
return Weather.Cold;
}
}
public static Weather Hot
{
get
{
return Weather.Hot;
}
}
}
In your ResourceDictionary:
<local:WeatherEnumValues x:Key="WeatherEnumValues" />
And here we are:
"{Binding whatever, Converter={StaticResource converterName},
ConverterParameter={Binding Hot, Source={StaticResource WeatherEnumValues}}}" />
This is an answer utilizing resources and without Converters:
View:
<Page
.....
xmlns:local="using:EnumNamespace"
.....
>
<Grid>
<Grid.Resources>
<local:EnumType x:Key="EnumNamedConstantKey">EnumNamedConstant</local:SettingsCats>
</Grid.Resources>
<Button Content="DoSomething" Command="{Binding DoSomethingCommand}" CommandParameter="{StaticResource EnumNamedConstantKey}" />
</Grid>
</Page>
ViewModel
public RelayCommand<EnumType> DoSomethingCommand { get; }
public SomeViewModel()
{
DoSomethingCommand = new RelayCommand<EnumType>(DoSomethingCommandAction);
}
private void DoSomethingCommandAction(EnumType _enumNameConstant)
{
// Logic
.........................
}

x:Bind ViewModel method to an Event inside DataTemplate

I'm basically asking the same question as this person, but in the context of the newer x:Bind.
ViewModels' DataContext is defined like so
<Page.DataContext>
<vm:ChapterPageViewModel x:Name="ViewModel" />
</Page.DataContext>
So whenever I need to bind something I do it explicitely to the ViewModel like so
ItemsSource="{x:Bind ViewModel.pageList, Mode=OneWay}"
However that doesn't work within templates
<FlipView ItemsSource="{x:Bind ViewModel.pageList, Mode=OneWay}">
<FlipView.ItemTemplate>
<DataTemplate x:DataType="models:Image">
<ScrollViewer SizeChanged="{x:Bind ViewModel.PageResized}"> <-- this here is the culprit
<Image Source="{x:Bind url}"/>
</ScrollViewer>
</DataTemplate>
</FlipView.ItemTemplate>
</FlipView>
Reading the documentation, I found that using Path should basically reset the context to the page, but this (x:Bind Path=ViewModel.PageResizeEvent didn't work either. I'm still getting Object reference not set to an instance of an object, which should mean that it doesn't see the method (but a null).
Image class:
public class Image {
public int page { get; set; }
public string url { get; set; }
public int width { get; set; }
public int heigth { get; set; }
}
And in the ChapterPageViewModel
private List<Image> _pageList;
public List<Image> pageList {
get { return _pageList; }
set { Set(ref _pageList, value); }
}
public override async Task OnNavigatedToAsync(object parameter, NavigationMode mode,
IDictionary<string, object> suspensionState)
{
Initialize();
await Task.CompletedTask;
}
private async void Initialize()
{
pageList = await ComicChapterGet.GetAsync(_chapterId);
}
public void PageResized(object sender, SizeChangedEventArgs e)
{
//resizing logic happens here
}
We have two problems here:
First, trying to directly bind an event to a event handler delegate
That will never work, simply put.
One way to handle an event on MVVM pattern is by using EventTrigger and ICommand.
It requires a class that implements ICommand. This post will help you if don't know how to do it. I'll call mine DelegateCommand.
Here's how I would refactor it in two steps:
1) Add a Command to the VM:
public class ChapterPageViewModel
{
public ChapterPageViewModel()
{
this.PageResizedCommand = new DelegateCommand(OnPageResized);
}
public DelegateCommand PageResizedCommand { get; }
private void OnPageResized()
{ }
}
2) Bind that Command to the SizeChanged event with EventTrigger and InvokeCommandAction.
<Page (...)
xmlns:i="using:Microsoft.Xaml.Interactivity"
xmlns:core="using:Microsoft.Xaml.Interactions.Core">
(...)
<FlipView ItemsSource="{x:Bind ViewModel.pageList, Mode=OneWay}" >
<FlipView.ItemTemplate>
<DataTemplate x:DataType="models:Image">
<ScrollViewer>
<i:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="SizeChanged">
<core:InvokeCommandAction
Command="{x:Bind ViewModel.PageResizedCommand }" />
</core:EventTriggerBehavior>
</i:Interaction.Behaviors>
<Image Source="{x:Bind url}"/>
</ScrollViewer>
</DataTemplate>
</FlipView.ItemTemplate>
</FlipView>
</Page>
"But Gabriel", you say, "that didn't work!"
I know! And that's because of the second problem, which is trying to x:Bind a property that does not belong to the DataTemplate class
This one is closely related to this question, so I´ll borrow some info from there.
From MSDN, regarding DataTemplate and x:Bind
Inside a DataTemplate (whether used as an item template, a content
template, or a header template), the value of Path is not interpreted
in the context of the page, but in the context of the data object
being templated. So that its bindings can be validated (and efficient
code generated for them) at compile-time, a DataTemplate needs to
declare the type of its data object using x:DataType.
So, when you do <ScrollViewer SizeChanged="{x:Bind ViewModel.PageResized}">, you're actually searching for a property named ViewModel on the that models:Image class, which is the DataTemplate's x:DataType. And such a property does not exist on that class.
Here, I can see two options. Choose one of them:
Add that ViewModel as a property on the Image class, and fill it up on the VM.
public class Image {
(...)
public ChapterPageViewModel ViewModel { get; set; }
}
public class ChapterPageViewModel
{
(...)
private async void Initialize() {
pageList = await ComicChapterGet.GetAsync(_chapterId);
foreach(Image img in pageList)
img.ViewModel = this;
}
}
With only this, that previous code should work with no need to change anything else.
Drop that x:Bind and go back to good ol'Binding with ElementName.
<FlipView ItemsSource="{x:Bind ViewModel.pageList, Mode=OneWay}" x:Name="flipView">
<FlipView.ItemTemplate>
<DataTemplate x:DataType="models:Image">
<ScrollViewer>
<i:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="SizeChanged">
<core:InvokeCommandAction
Command="{Binding DataContext.PageResizedCommand
, ElementName=flipView}" />
</core:EventTriggerBehavior>
</i:Interaction.Behaviors>
<Image Source="{x:Bind url}"/>
</ScrollViewer>
</DataTemplate>
</FlipView.ItemTemplate>
</FlipView>
This one kind of defeat the purpose of your question, but it does work and it's easier to pull off then the previous one.

How to bind a method in my viewmodel to TextChanged?

I'm having a bit of trouble figuring out how to correctly bind my method to the viewmodel.
Here is my current XAML:
<TextBox x:Name="Length" Style="{StaticResource LengthStyle}" Height="Auto" Width="35"
TextChanged="{Binding Validate}" Text="{Binding Length, ValidatesOnDataErrors=True, UpdateSourceTrigger=LostFocus}" />
Then in my viewmodel I have a method that looks like this:
public string Validate(Column c){
//unimportant
}
I'm just confused on how I can get this to work. Should I have a setter property that calls this? I would have just set this up as an event in the code behind but the project I'm working on prohibits that. Thanks.
Create a property like the following
private string length;
public string Length
{
get
{
return length;
}
set
{
length = value;
//do whatever you want
}
}
Include these 2 interactivity references :
1. System.Windows.Interactivity
2. Microsoft.Expression.Interactions
Then in your xaml declare this :
xmlns:interactivity="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:interactions="clr-namespace:Microsoft.Expression.Interactivity.Input;assembly=Microsoft.Expression.Interactions"
The xaml for textbox will be like :
<TextBox>
<interactivity:Interaction.Triggers>
<interactivity:EventTrigger EventName="TextChanged">
<behaviours:ExecuteCommandAction Command="{Binding Path=DataContext.ValidateCommand,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type UserControl}}}"
CommandParameter="PassTheColumnHere"/>
</interactivity:EventTrigger>
</interactivity:Interaction.Triggers>
</TextBox>
You may need to make 2 changes as per your requirement:
1. Instead of Ancestortype "Usercontrol"(if your xaml is not a user control") , then give the file name.
2.Pass the command parameter, in your case the column as you are mentioning.
After that declare the command "ValidateCommand" in your view model:
public ICommand ValidateCommand{ get; private set; }
Inside the constructor initialize it:
ValidateCommand = new DelegateCommand<Column>(Validate);
and the rest logic you can implement in your method:
public void Validate(Column c){
//your logic
}

Bind enum value to attached property in XAML

I have the following class which has an enum and an attached property.
namespace U.Helpers
{
public enum SearchDirection
{
Forward,
Backward
}
public class TargetedTriggerActionFindNextButton : TargetedTriggerAction<DataGrid>
{
protected override void Invoke(object parameter)
{
if (SearchDirectionControl == SearchDirection.Forward)
//Do something
else
//Do something else
}
public static readonly DependencyProperty SearchDirectionControlProperty =
DependencyProperty.Register("SearchDirectionControl", typeof(object), typeof(TargetedTriggerActionFindNextButton), new PropertyMetadata(SearchDirection.Forward));
public SearchDirection SearchDirectionControl
{
get { return (SearchDirection)GetValue(SearchDirectionControlProperty); }
set { SetValue(SearchDirectionControlProperty, value); }
}
}
}
Here is my XAML so far:
<UserControl x:Class="UM.LaunchPad"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:helpers="clr-namespace:UM.Helpers">
<Grid Name="gridUsers" Background="Transparent">
<Button Name="SearchNextButton" Content="Next" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<helpers:TargetedTriggerActionFindNextButton TargetObject="{Binding ElementName=GenericDataGrid}"
SearchDirectionControl="{Binding helpers:SearchDirection.Forward}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</Grid>
</UserControl>
I am wondering how to add an enum value to my attached property on the XAML side. See the button's SearchDirectionControl.
Do not use a binding when you want to refer to a specific Enum value, but use the x:Static Markup Extension instead:
<helpers:TargetedTriggerActionFindNextButton
TargetObject="{Binding ElementName=GenericDataGrid}"
SearchDirectionControl="{x:Static helpers:SearchDirection.Forward}"
/>
Quoting the MSDN documentation of x:Static:
References any static by-value code entity that is defined in a Common Language Specification (CLS)–compliant way.
[...]
The code entity that is referenced must be one of the following:
A constant
A static property
A field [sic; it should be a "static field", obviously]
An enumeration value

Grouping related ICommands

I'm new to C# and this is my first WPF project. I am following this tutorial (using their implementation of RelayCommand and attempting to use MVVM. I am implementing a clone of the standard Windows calculator. I would like to find a way to group functionality of similar buttons as what I am doing seems clumsy.
For exmaple, here is my my XAML of three buttons
<Button Content="_7" Focusable ="False" Command="{Binding Seven}" Style="{DynamicResource NumberButton}" Margin="0,134,184,129"/>
<Button Content="_8" Focusable ="False" Command="{Binding Eight}" Style="{DynamicResource NumberButton}" Margin="46,134,138,129"/>
<Button Content="_9" Focusable ="False" Command="{Binding Nine}" Style="{DynamicResource NumberButton}" Margin="92,134,92,129"/>
Here is the ICommands for those:
public ICommand Nine { get { return new RelayCommand(NineExecute); } }
public ICommand Eight { get { return new RelayCommand(EightExecute); } }
public ICommand Seven { get { return new RelayCommand(SevenExecute); } }
and the methods:
void NineExecute()
{
NumberPressed("9");
}
void EightExecute()
{
NumberPressed("8");
}
void SevenExecute()
{
NumberPressed("7");
}
What should I investigate in order to group similar function buttons such as numbers into a single ICommand, with a single method that can determine the sender - while still not putting code behind in the Window class as the article warns against.
Xlam code for a button (supposing that you defined your data context):
<....DataContext>
<loc:Commands/>
</....DataContext>
<Button Content="_9"
Command="{Binding Path=ShowMeABox}"
CommandParameter="{Binding RelativeSource={RelativeSource Mode=Self}, Path=Content}"/>
Our dummy command (using RelayCommand<T> from the provided link):
public class Commands
{
private static readonly ICommand _showShowBoxCommand =
new RelayCommand<string>(str => MessageBox.Show(str));
public static ICommand ShowMeABox { get { return _showShowBoxCommand; } }
}
That's it.
FYI.
It's seems that you explicitly specify button size which is generally a bad practice. To position your buttons use stack or wrap panel, or grid/uniformgrid.
Read info on styles and templates to increase code reuse.
Example:
<UniformGrid Columns="3" Rows="3">
<UniformGrid.DataContext>
<loc:Commands/>
</UniformGrid.DataContext>
<UniformGrid.Resources>
<Style TargetType="Button">
<Setter Property="Command" Value="{Binding ShowMeABox}"/>
<Setter Property="CommandParameter" Value="{Binding RelativeSource={RelativeSource Mode=Self}, Path=Content}"/>
</Style>
</UniformGrid.Resources>
<Button Content="_1"/>
<Button Content="_2"/>
<Button Content="_3"/>
<Button Content="_4"/>
<Button Content="_5"/>
<Button Content="_6"/>
<Button Content="_7"/>
<Button Content="_8"/>
<Button Content="_9"/>
</UniformGrid>
May be it's possible to bind Enumerable.Range(0,10) to populate control automatically in the MVVM fashion.
Good luck!
Use the CommandParameter property - that way you can bind all of your buttons to the same Command but with different CommandParameter for each number (ie, the CommandParameter should be an integer representing which button as actually pressed)
I would personally write this using a constructor:
public YourType()
{
this.Seven = new RelayCommand( () => NumberPressed("7"));
this.Eight = new RelayCommand( () => NumberPressed("8"));
this.Nine = new RelayCommand( () => NumberPressed("9"));
}
public ICommand Nine { get; private set; }
public ICommand Eight { get; private set; }
public ICommand Seven { get; private set; }
Note that it may also make sense to use the CommandParameter, and a single command. Many frameworks provide a variation on RelayCommand/ActionCommand/etc which accept a command parameter, which you can then specify in XAML. That specific implementation doesn't allow it, but many do, and will map to a method which gets the CommandParameter as a parameter directly.

Categories

Resources