I don't know if need to combine DataTrigger & Trigger, if there's better way please tell me.
My goal is, to create a menu(with icons), icons will change while meet hover or selected event.
Here's an enum define all menu types:
public enum PageTypes:byte
{
NotSet = 0,
HomePage = 1,
ShopPage = 2,
AboutPage = 3
}
Then I created a MenuItemModel represent each menu item:
public class MenuItemModel : INotifyPropertyChanged
{
private PageTypes _menuItemType = PageTypes.NotSet;
public PageTypes MenuItemType { get { return _menuItemType; } set { if (value != _menuItemType) { _menuItemType = value; RaisePropertyChanged(() => MenuItemType); } } }
private bool _isSelected = false;
public bool IsSelected { get { return _isSelected; } set { if (value != _isSelected) { _isSelected = value; RaisePropertyChanged(() => IsSelected); } } }
}
Ok, then I begin to create UI.
<!-- MenuItem Template -->
<DataTemplate x:Key="MenuTemplate">
<Button Command="{Binding ClickCommand}" CommandParameter="{Binding}">
<Image>
<Image.Style>
<Style TargetType="Image">
<Setter Property="Source" Value="/Image/Home_normal.png"/>
<Style.Triggers>
<DataTrigger Binding="{Binding MenuItemType}" Value="ShopPage">
<Setter Property="Source" Value="/Image/Shop_normal.png"/>
</DataTrigger>
<DataTrigger Binding="{Binding MenuItemType}" Value="AboutPage">
<Setter Property="Source" Value="/Image/About_normal.png"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</Button>
</DataTemplate>
till now everything is very easy, but when I try to make mouseOver and Selected effect, problem comes.
for example, if mouse over home_normal.png, it should change to home_hover.png, if IsSelected property is TRUE, image should be ignore hover trigger then use home_selected.png. But there's 3 image, how do I know what image should change?
<!-- MenuItem Template -->
<DataTemplate x:Key="MenuTemplate">
<Button Command="{Binding ClickCommand}" CommandParameter="{Binding}">
<Image>
<Image.Style>
<Style TargetType="Image">
<Setter Property="Source" Value="/Image/Home_normal.png"/>
<Style.Triggers>
<DataTrigger Binding="{Binding MenuItemType}" Value="ShopPage">
<Setter Property="Source" Value="/Image/Shop_normal.png"/>
</DataTrigger>
<DataTrigger Binding="{Binding MenuItemType}" Value="AboutPage">
<Setter Property="Source" Value="/Image/About_normal.png"/>
</DataTrigger>
<!-- MY PLAN -->
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Source" Value="?_hover.png"/>
</Trigger>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<Setter Property="Source" Value="?_selected.png"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</Button>
</DataTemplate>
If you can see the question mark in "MY PLAN" comment, that would be my question: what should I do in the Value field?
You can use MultiDataTrigger like this. But you should add same 3 triggers for all types of pages. Note that next trigger overrides below and conditions works like logical AND.
<p:Style.Triggers xmlns:p="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<DataTrigger Binding="{Binding MenuItemType}" Value="ShopPage">
<Setter Property="Source" Value="/Image/Shop_normal.png"/>
</DataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding MenuItemType}" Value="ShopPage" />
<Condition Binding="{Binding RelativeSource={RelativeSource Mode=Self}, Path=IsMouseOver}" Value="true" />
</MultiDataTrigger.Conditions>
<Setter Property="Source" Value="/Image/Shop_MouseOver.png" />
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding MenuItemType}" Value="ShopPage" />
<Condition Binding="{Binding RelativeSource={RelativeSource Mode=Self}, Path=IsSelected}" Value="true" />
</MultiDataTrigger.Conditions>
<Setter Property="Source" Value="/Image/Shop_IsSelected.png" />
</MultiDataTrigger>
</p:Style.Triggers>
In my opinion, the answer you've already received and accepted is a good one. It's entirely XAML-based, which seems to be a primary goal in your scenario, and it should work very well. That said, the XAML-only solution is fairly verbose and involves a lot of redundant code. This is already seen in the scenario above where you have two buttons types, each with three possible states. And it will only get worse as you add button types and states.
If you are willing to do a little code-behind, I think you can accomplish the same effect but with a lot less redundancy.
Specifically, if you use <MultiBinding>, you can bind the relevant properties to a collection that can be used to look up the correct image source. In order for me to accomplish this, I needed to create a couple of small container types to store the lookup data, and of course the IMultiValueConverter implementation to use them:
Container types:
[ContentProperty("Elements")]
class BitmapImageArray
{
private readonly List<ButtonImageStates> _elements = new List<ButtonImageStates>();
public List<ButtonImageStates> Elements
{
get { return _elements; }
}
}
class ButtonImageStates
{
public string Key { get; set; }
public BitmapImage[] StateImages { get; set; }
}
Converter:
class OrderedFlagConverter : IMultiValueConverter
{
public object Convert(object[] values,
Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
BitmapImageArray imageData = (BitmapImageArray)parameter;
string type = (string)values[0];
foreach (ButtonImageStates buttonStates in imageData.Elements)
{
if (buttonStates.Key == type)
{
int index = 1;
while (index < values.Length)
{
if ((bool)values[index])
{
break;
}
index++;
}
return buttonStates.StateImages[index - 1];
}
}
return DependencyProperty.UnsetValue;
}
public object[] ConvertBack(object value,
Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
In your example, using the above might look something like this:
<DataTemplate x:Key="MenuTemplate">
<Button Command="{Binding ClickCommand}" CommandParameter="{Binding}">
<Image>
<Image.Source>
<MultiBinding>
<MultiBinding.Converter>
<l:OrderedFlagConverter/>
</MultiBinding.Converter>
<MultiBinding.ConverterParameter>
<l:BitmapImageArray>
<l:ButtonImageStates Key="ShopPage">
<l:ButtonImageStates.StateImages>
<x:Array Type="{x:Type BitmapImage}">
<BitmapImage UriSource="/Image/Shop_selected.png"/>
<BitmapImage UriSource="/Image/Shop_hover.png"/>
<BitmapImage UriSource="/Image/Shop_normal.png"/>
</x:Array>
</l:ButtonImageStates.StateImages>
</l:ButtonImageStates>
<l:ButtonImageStates Key="AboutPage">
<l:ButtonImageStates.StateImages>
<x:Array Type="{x:Type BitmapImage}">
<BitmapImage UriSource="/Image/About_selected.png"/>
<BitmapImage UriSource="/Image/About_hover.png"/>
<BitmapImage UriSource="/Image/About_normal.png"/>
</x:Array>
</l:ButtonImageStates.StateImages>
</l:ButtonImageStates>
</l:BitmapImageArray>
</MultiBinding.ConverterParameter>
<Binding Path="ButtonType"/>
<Binding Path="IsMouseOver" RelativeSource="{RelativeSource Self}"/>
<Binding Path="IsSelected"/>
</MultiBinding>
</Image.Source>
</Image>
</Button>
</DataTemplate>
The converter takes, as input, bindings to the properties that affect the visual state of the button. The first bound value is simply the type of the button; this is used to look up the correct array of button states for the button. The remaining bound values (you can have arbitrarily many in this approach) are flags that are searched; the images are stored in the same order as the flags, with one additional "default" image at the end (i.e. if no flags are set, the default image is returned).
In this way, adding new button types involves only adding a new ButtonImageStates object, specifying the correct key for that button type, and adding new button states involves only adding a single line to each button type's list: the BitmapImage reference that corresponds to the image for that state for that button type.
Doing it this way drastically cuts down on the amount of code one has to add as new button types and states are needed: a given button type need be mentioned in the XAML only once, and likewise each triggering property is mentioned only once. A XAML-only approach will require a lot of duplicated boilerplate, and the actual image file references will be scattered throughout the style declaration.
Here is a simple demo of the basic technique. Lacking a good MCVE to start with, I didn't want to waste time re-creating parts of the code that weren't strictly necessary for the purposes of a demonstration:
I only bothered to create four state images, and of course only wrote code to deal with four possible states: two each for two different button types.
I also didn't bother with putting this in a menu; I'm just using a plain ItemsControl to present the buttons.
Naturally, the view model is a degenerate class; I didn't bother with property-change notification, since it's not needed here. The example still works if you include that though.
Here are the images used in the example (I'm a programmer, not an artist…I considered not even bothering with image content, since that's also not strictly required to demonstrate the basic technique, but figured I could handle four basic images :) ):
These are added to the project in a "Resources" folder, with the Build Action set to Resource.
XAML:
<Window x:Class="TestSO34193266MultiTriggerBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:TestSO34193266MultiTriggerBinding"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<l:OrderedFlagConverter x:Key="orderedFlagConverter1"/>
<BitmapImage x:Key="bitmapRedNormal"
UriSource="pack://application:,,,/Resources/red_normal.png"/>
<BitmapImage x:Key="bitmapRedHover"
UriSource="pack://application:,,,/Resources/red_hover.png"/>
<BitmapImage x:Key="bitmapGreenNormal"
UriSource="pack://application:,,,/Resources/green_normal.png"/>
<BitmapImage x:Key="bitmapGreenHover"
UriSource="pack://application:,,,/Resources/green_hover.png"/>
<l:ViewModel x:Key="redViewModel" ButtonType="Red"/>
<l:ViewModel x:Key="greenViewModel" ButtonType="Green"/>
<x:Array x:Key="items" Type="{x:Type l:ViewModel}">
<StaticResource ResourceKey="redViewModel"/>
<StaticResource ResourceKey="greenViewModel"/>
</x:Array>
<x:Array x:Key="redButtonStates" Type="{x:Type BitmapImage}">
<StaticResource ResourceKey="bitmapRedHover"/>
<StaticResource ResourceKey="bitmapRedNormal"/>
</x:Array>
<x:Array x:Key="greenButtonStates" Type="{x:Type BitmapImage}">
<StaticResource ResourceKey="bitmapGreenHover"/>
<StaticResource ResourceKey="bitmapGreenNormal"/>
</x:Array>
<l:BitmapImageArray x:Key="allButtonStates">
<l:ButtonImageStates Key="Red" StateImages="{StaticResource redButtonStates}"/>
<l:ButtonImageStates Key="Green" StateImages="{StaticResource greenButtonStates}"/>
</l:BitmapImageArray>
<ItemsPanelTemplate x:Key="panelTemplate">
<StackPanel IsItemsHost="True" Orientation="Horizontal"/>
</ItemsPanelTemplate>
<DataTemplate x:Key="template" DataType="l:ViewModel">
<Button>
<Image Stretch="None">
<Image.Source>
<MultiBinding Converter="{StaticResource orderedFlagConverter1}"
ConverterParameter="{StaticResource allButtonStates}">
<Binding Path="ButtonType"/>
<Binding Path="IsMouseOver" RelativeSource="{RelativeSource Self}"/>
</MultiBinding>
</Image.Source>
</Image>
</Button>
</DataTemplate>
<!-- explicit namespace only for the benefit of Stack Overflow formatting -->
<p:Style TargetType="ItemsControl"
xmlns:p="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<Setter Property="ItemsSource" Value="{StaticResource items}"/>
<Setter Property="ItemsPanel" Value="{StaticResource panelTemplate}"/>
</p:Style>
</Window.Resources>
<StackPanel>
<ItemsControl ItemTemplate="{StaticResource template}"/>
</StackPanel>
</Window>
C#:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
class ViewModel
{
public string ButtonType { get; set; }
}
class OrderedFlagConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
BitmapImageArray imageData = (BitmapImageArray)parameter;
string type = (string)values[0];
foreach (ButtonImageStates buttonStates in imageData.Elements)
{
if (buttonStates.Key == type)
{
int index = 1;
while (index < values.Length)
{
if ((bool)values[index])
{
break;
}
index++;
}
return buttonStates.StateImages[index - 1];
}
}
return DependencyProperty.UnsetValue;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
[ContentProperty("Elements")]
class BitmapImageArray
{
private readonly List<ButtonImageStates> _elements = new List<ButtonImageStates>();
public List<ButtonImageStates> Elements
{
get { return _elements; }
}
}
class ButtonImageStates
{
public string Key { get; set; }
public BitmapImage[] StateImages { get; set; }
}
One minor note: for some reason I get in the XAML editor the following error message on the <Window> element declaration:
Collection property 'TestSO34193266MultiTriggerBinding.ButtonImageStates'.'StateImages' is null.
I've clearly failed to jump through some hoop the XAML editor wants me to clear with respect to the declaration and/or implementation of ButtonImageStates, but what that is I don't know. The code compiles and runs just fine, so I haven't bothered to try to figure that part out. It may well be the case that there's a better way to represent the map of button state images, but this way works and other than the spurious error seems fine to me.
Related
I am getting my buttons to appear, but their styling is not displaying and i'm not sure why.
Below is the XAML code and how it is defined in the view model.
private ObservableCollection<Button> myButtons;
public ObservableCollection<Button> MyButtons
{
get { return myButtons; }
set
{
if (myButtons == null)
{
myButtons = value; OnPropertyChanged("MyButtons");
}
}
}
private void PopulateButtons()
{
List<Button> buttonsToAdd = new List<Button>();
List<string> buttonsToAdd = new List<string>();
foreach (var item in SettingsSingleton.RowColumnOptions)
{
int total = item.Key;
Tuple<int, int> rowColumn = item.Value;
buttonsToAdd.Add((total).ToString());
}
MyButtons = new ObservableCollection<Button>(buttonsToAdd);
}
And XAML is
<StackPanel DockPanel.Dock="Top"
HorizontalAlignment="Center" VerticalAlignment="Center">
<ItemsControl ItemsSource="{Binding MyButtons}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Command="{Binding Create_Click}" CommandParameter="{Binding Content}">
<Button.Style>
<Style TargetType="Button" BasedOn="{StaticResource NiceStyleButton}">
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Margin" Value="20"/>
<Setter Property="Padding" Value="6"/>
<Setter Property="FontSize" Value="42"/>
</Style>
</Button.Style>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
I also get a weird warning/error:
System.Windows.Data Error: 26 : ItemTemplate and ItemTemplateSelector are ignored for items already of the ItemsControl's container type; Type='Button'
Can anyone help? Thank you.
EDIT:
My Create_Click command and the functions that then get called by it.
private ICommand createClickCommand;
public ICommand Create_Click
{
get
{
if (createClickCommand == null)
{
createClickCommand = new RelayCommand(CreateGrid);
}
return createClickCommand;
}
}
private void CreateGrid(object param)
{
Generate(param);
}
And RowColumnOptions is basically just a tuple of int, int, where we define a row and column. This is used for the Grid creation. This is where RowColumnOptions gets populated.
public static class SystemSettingsSingleton
{
public static Dictionary<int, Tuple<int, int>> RowColumnOptions = new Dictionary<int, Tuple<int, int>>();
public static void SetOptions(List<string> Options)
{
// Let's parse our options
foreach (var option in Options)
{
var rowAndColumnSettings = option.Split('x');
// Check that we have both row and column count
int rowCount, columnCount = 0;
if (rowAndColumnSettings.Length == 2 && int.TryParse(rowAndColumnSettings[0], out rowCount)
&& int.TryParse(rowAndColumnSettings[1], out columnCount))
{
RowColumnOptions.Add( (rowCount * columnCount),
new Tuple<int, int>(rowCount, columnCount) );
}
}
}
}
That SetOptions method is coming from a static class.
You shouldn't have an observable collection of Button. That's never a good idea. It's telling you it's ignoring the ItemTemplate because you are giving it controls in the collection, which is unnecessary. Then you're trying to create another button in the item template. Templates in WPF don't mean what you think they do: They don't style content, they create content. Don't create buttons in your viewmodel, create them in your item template. Your ObservableCollection should just provide the information the item template will need to create the buttons you want to see. Here, that's just one string.
But it turns out that your options really consist of tuples. You want to display their products in the button content, and you want to pass the tuples themselves as the command parameter. This is easy.
public class ViewModel : ViewModelBase
{
public Dictionary<int, Tuple<int, int>> RowColumnOptions
=> SystemSettingsSingleton.RowColumnOptions;
private void PopulateButtons()
{
OnPropertyChanged(nameof(RowColumnOptions));
}
// I don't know what your relay command class looks like, I just tossed one
// together that has an Action<object>
public ICommand Create_Click { get; } = new RelayCommand(param =>
{
var tuple = param as Tuple<int, int>;
MessageBox.Show($"Parameter: {tuple.Item1} x {tuple.Item2}");
});
}
And the ItemsControl:
<ItemsControl ItemsSource="{Binding RowColumnOptions}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<!--
The DataContext here is a KeyValuePair<int, Tuple<int, int>>
The product of x * y was the Key, so that's what we'll display in the button's
Content.
We could display all three values if we wanted to.
We want to pass the tuple to the command, and that's the Value of the KeyValuePair.
So we bind that to CommandParameter
-->
<Button
Command="{Binding DataContext.Create_Click, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
CommandParameter="{Binding Value}"
Content="{Binding Key}"
>
<Button.Style>
<Style TargetType="Button" BasedOn="{StaticResource NiceStyleButton}">
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Margin" Value="20"/>
<Setter Property="Padding" Value="6"/>
<Setter Property="FontSize" Value="42"/>
</Style>
</Button.Style>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Some questions linger in my mind: What if you have both "2x6" and "3x4" in that initial list of options? The first one that comes in will be replaced by the second. Is that the desired behavior?
Given different screen sizes what is the accepted method of scaling the UI?
In setting up a UI it looks great on one screen but terrible on another. Trying to set up a possibly dynamic style based on screen dimensions. I have here a simple header with a FormattedString in a label. I want to center the entire label with the spans formatting remaining intact. Ideally I'd like to set the height of the text to some percentage of the Current.MainPage.Height ...
From App.xaml
<Application.Resources>
<ResourceDictionary>
<Style x:Key="HeaderSpans" TargetType="Span" >
<Setter Property="BackgroundColor" Value="Transparent"></Setter>
<Setter Property="HorizontalOptions" Value="Center"></Setter>
<Setter Property="TextColor" Value="White"></Setter>
<Setter Property="VerticalTextAlignment" Value="Center"></Setter>
<Setter Property="Margin" Value="0, 20, 0, 0"></Setter>
</Style>
<Style x:Key="HeaderSpan" TargetType="Span" >
<Setter Property="TextColor" Value="White"></Setter>
<Setter Property="FontSize" Value="32"></Setter>
</Style>
<Style x:Key="HeaderSpanB" TargetType="Span" >
<Setter Property="TextColor" Value="White"></Setter>
<Setter Property="FontSize" Value="32"></Setter>
<Setter Property="FontAttributes" Value="Bold"></Setter>
</Style>
</ResourceDictionary>
</Application.Resources>
Code behind
switch (Device.RuntimePlatform)
{
case Device.iOS:
//MainPage.BackgroundColor = Color.Black;
break;
case Device.Android:
//MainPage.BackgroundColor = Color.Red;
break;
case Device.UWP:
//MainPage.BackgroundColor = Color.Orange;
break;
default:
//MainPage.BackgroundColor = Color.Transparent;
break;
}
I thought that I might be able to utilize this code to do the deed. But I don't know how to impact the styles from there. I thought that a setter might be the right path. I have not made solid progress.
From Header.xaml
<!-- dark-blue backing header -->
<Image Source="Header752x135.png" VerticalOptions="Start" HorizontalOptions="CenterAndExpand"></Image>
<!-- SHIPSHAPE text placed on the backing header -->
<Label
Style="{StaticResource HeaderSpans}"
>
<Label.FormattedText>
<FormattedString>
<Span Text="SHIP" Style="{StaticResource HeaderSpan}" />
<Span Text="SHAPE" Style="{StaticResource HeaderSpanB}" />
</FormattedString>
</Label.FormattedText>
</Label>
With no code behind.
I would be appreciative if anyone could lead me to the correct solution.
If you want to change values of your UI depending on the platform, you could make use of the "On Platform" Statement.
For instance, if you want to have a different margin for a grid on iOS than you need to use on Android, you could use it like that:
<Grid>
<Grid.Margin>
<On Platform x:TypeArguments="Thickness">
<On Platform="iOS">0,0,0,0</On>
<On Platform="Android">15,0,15,0</On>
</OnPlatform>
</Grid.Margin>
</Grid>
Of course you can use that for other properties as well. Keep in mind that if you set a property in your view.xaml it will override the style definition of the same property if present.
Making your font size dependent on the screen height can be done as follows:
Getting the screen dimensions - Shared
We will need to implement a dependency service, which allows us to retrieve the actual screen height or width.
Therefore in the shared code, create a new interface:
IScreenDimensions.cs
public interface IScreenDimensions
{
double GetScreenWidth();
double GetScreenHeight();
}
in your android implementation, add the implementation of the interface:
Getting the screen dimensions - Android
ScreenDimensions.cs
[assembly: Dependency(typeof(ScreenDimensions))]
namespace MyAppNamespace.Droid
{
public class ScreenDimensions : IScreenDimensions
{
public double GetScreenHeight()
{
return ((double)Android.App.Application.Context.Resources.DisplayMetrics.HeightPixels / (double)Android.App.Application.Context.Resources.DisplayMetrics.Density);
}
public double GetScreenWidth()
{
return ((double)Android.App.Application.Context.Resources.DisplayMetrics.WidthPixels / (double)Android.App.Application.Context.Resources.DisplayMetrics.Density);
}
}
}
Getting the screen dimensions - iOS
ScreenDimensions.cs
[assembly: Dependency(typeof(ScreenDimensions))]
namespace MyAppNamespace.iOS
{
public class ScreenDimensions : IScreenDimensions
{
public double GetScreenHeight()
{
return (double)UIScreen.MainScreen.Bounds.Height;
}
public double GetScreenWidth()
{
return (double)UIScreen.MainScreen.Bounds.Width;
}
}
}
Building a value converter to consume the screen dimensions
Now we create an IValueConverter (again in shared code):
ScreenSizeToRelativeSizeConverter.cs
public class ScreenSizeToRelativeSizeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
double height = DependencyService.Get<IScreenDimensions>().GetScreenHeight();
return (double) height * (double) parameter;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
// no need to convert anything back
throw new NotImplementedException();
}
}
Note that the converter needs a parameter that will tell it which fraction of the screen size the resulting size will end up with.
Putting it all together
Finally you add the following resources to your App.xaml file:
<Application.Resources>
<ResourceDictionary>
<converter:ScreenSizeToRelativeSizeConverter x:Key="SizeToRelativeSizeConverter"/>
<x:Double x:Key="fontSizeFactor">0.03</x:Double>
<Style x:Key="LabelStyle" TargetType="Label">
<Setter Property="FontSize" Value="{Binding Converter={StaticResource SizeToRelativeSizeConverter}, ConverterParameter={StaticResource fontSizeFactor}}" />
</Style>
</ResourceDictionary>
</Application.Resources>
And set the style to your label (or other element) in question:
<Label Text="Welcome to Xamarin.Forms!" Style="{StaticResource LabelStyle}"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
I have one int value in my Vmodel :
public int MaxTagCount => URLsCount.Max(tag => tag.Count);
And I need to connect this MaxTagCount with Trigger :
<DataTrigger Binding="{Binding Count}" Value="1149">
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Foreground" Value="Green"/>
</DataTrigger>
How can I replace "1149" to MaxTagCount ?
If i understand you correctly, you are looking for a way to bind the Value of the DataTrigger to your MaxTagCount property, which isn't possible due to the fact that Value isn't a dependency property.
The most common workaround that is to pass both the MaxTagCount property and the Count property to a MultiValueConverter, the converter will compare those two values and return true or false. The role of the DataTrigger now will be to check the value returned by the converter so:
First, define a basic converter that compares two values like so:
public class CompareValuesConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values?[0].Equals(values[1]);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Second, update your DataTrigger to check the returned value of the converter, and pass your values to the converter, and set your style accordingly:
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding >
<MultiBinding.Converter>
<local:CompareValuesConverter/>
</MultiBinding.Converter>
<Binding Path="Count" />
<Binding Path="DataContext.MaxTagCount" ElementName="Main"/>
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Foreground" Value="Green"/>
</DataTrigger>
Notice that i am using an ElementName binding to get the MaxTagCount value since it is (most likely) defined on the global UI DataContext (in this case the main Window is Named Main), you could also use a RelativeSource binding.
As already explained by #Elhamer, you can't bind to the Value property of a DataTrigger because it is not a dependency property.
As an alternative to using a multi converter, you could just add another property to your view model that returns a bool that indicates whether the Count and MaxTagCount properties are equal:
public bool IsMax => Count == MaxCount;
...and bind to this one:
<DataTrigger Binding="{Binding IsMax}" Value="True">
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Foreground" Value="Green"/>
</DataTrigger>
After all, a view model is nothing but a model for the view and this kind of logic makes perfect sense to implement there.
I am wondering if anyone could explain me the difference between
binding a selected value of a Collection to a comboBox.
Or Binding the value to a Button Content.
Like that
<ComboBox x:Name="_culturedTitleViewModelSelector" Visibility="Hidden" Style="{StaticResource ResourceKey=_culturedTitleViewModelSelectorStyle}"
ItemsSource="{Binding Path=AvailableCultures, Source={x:Static Localized:ResourcesManager.Current}}"
SelectedValue="{Binding Path=CurrentCulture, Source={x:Static Localized:ResourcesManager.Current}}"
<Button x:Name="LanguageBtn" Content="{Binding Path=CurrentCulture, Source={x:StaticLocalized:ResourcesManager.Current}}"
The issue is If i Don't use the ComboBox up there, the DependencyProperty I Have in another class is not being called.
But if I Use the comboBox everything works...
Altought the comboBox doesnt do anything it's just a "workarround"
In my CS code when i CLick on my button I DO that :
ResourcesManager.Current.SwitchToNextCulture();
//We use a dummy comboBox to make sure the LanguageBehavior Property is being notified.
_culturedTitleViewModelSelector.SelectedItem = ResourcesManager.Current.CurrentCulture;
And if I Dont set the SelectedItem of the combobox to another culture. My languageBehavior class is not notified.
:
public class LanguageBehavior
{
public static DependencyProperty LanguageProperty =
DependencyProperty.RegisterAttached("Language",
typeof(string),
typeof(LanguageBehavior),
new UIPropertyMetadata(OnLanguageChanged));
public static void SetLanguage(FrameworkElement target, string value)
{
target.SetValue(LanguageProperty, value);
}
public static string GetLanguage(FrameworkElement target)
{
return (string)target.GetValue(LanguageProperty);
}
private static void OnLanguageChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
var element = target as FrameworkElement;
if (e.NewValue!=null)
element.Language = XmlLanguage.GetLanguage(e.NewValue.ToString());
}
}
I'd expect ComboBox Content to work the same as Button Content.
In my Generic.Xaml i do that :
<Style TargetType="{x:Type TextBlock}" x:Key="_textBlockLanguageProperty">
<Setter Property="WpfServices:LanguageBehavior.Language" Value="{Binding Path=CurrentCulture, Source={x:Static Localized:ResourcesManager.Current}}"
/>
</Style>
And that is CurrentCulture
public CultureInfo CurrentCulture
{
get { return CultureProvider.Current; }
set
{
if (value != CultureProvider.Current)
{
CultureProvider.Current = value;
OnCultureChanged();
}
}
}
Current :
public static ResourcesManager Current
{
get
{
if (_resourcesManager == null)
{
var cultureProvider = new BaseCultureProvider();
_resourcesManager = new ResourcesManager(cultureProvider);
_resourcesManager.Init();
}
return _resourcesManager;
}
}
EDIT :
My _culturedTitelViewModelSelectorStyle is
<Style TargetType="{x:Type ComboBox}" x:Key="_culturedTitleViewModelSelectorStyle">
<Setter Property="DisplayMemberPath" Value="DisplayName" />
<Setter Property="SelectedValuePath" Value="." />
<Setter Property="HorizontalAlignment" Value="Right" />
<Setter Property="MaxHeight" Value="40" />
<Setter Property="FontSize" Value="20" />
<Setter Property="Margin" Value="5" />
<Setter Property="SelectedIndex" Value="0" />
<Setter Property="IsSynchronizedWithCurrentItem" Value="True" />
</Style>
In the ComboBox you are binding the SelectedValue to a specific culture. This will select that culture from the list of available cultures, and therefor, trigger a set on the CurrentCulture property.
The Content property of a Button is merely displaying something to the user, it is not doing any assigning. It reads the property value and then displays it. That is why you need to manually change the Culture in the Click event to get it to do anything.
If you want the user to be able to select a value from a list of available values, a ComboBox or ListBox is the way to go. A Button is for triggering a specific action, not for selecting from a list.
i'm developing a WPF (.NET 3.5) application where i need to validate atextbox with a regular expression to match Empty textbox or text like 02145 or 05145 or 02145,05879,02445. the expression i use is ^(0(2|5)[0-9]{3})?((,0(2|5)[0-9]{3})*?)$.
It almost works just that i won't let me have empty textbox. here is some code
<Window.Resources>
<data:Message x:Key="message"/>
<Style x:Key="validButton" TargetType="{x:Type Button}" BasedOn="{StaticResource {x:Type Button}}" >
<Setter Property="IsEnabled" Value="False"/>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<!-- ......-->
<Condition Binding="{Binding ElementName=txtNumbers, Path=(Validation.HasError)}" Value="false"/>
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="True" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
<TextBox Height="23" Margin="-194.5,-88,-195.5,0" Name="txtNumbers" VerticalAlignment="Top" Style="{StaticResource txtboxerrors}">
<TextBox.Text>
<Binding Path="Numbers" Source="{StaticResource message}" ValidatesOnDataErrors="True" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<ExceptionValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<Button Height="23" Margin="0,0,-81,-189" Name="btnSendSMS" VerticalAlignment="Bottom" HorizontalAlignment="Right" Width="75" Click="btnSubmit_Click" Style="{StaticResource validButton}">Submit</Button>
And Class used for validation is below
class Message :IDataErrorInfo
{
//...
private string numbers;
public string this[string columnName]
{
get
{
string result = null;
//.....
if (columnName == "Numbers")
{
//multicellRegex has the ^(0(2|5)[0-9]{3})?((,0(2|5)[0-9]{3})*?)$ expression if(!Util.ValidateRegexPatern(Properties.Resources.multicellRegex,this.numbers))
{
result = "Number not in the correct format.try 020xx or 05xxx,026xx";
}
}
return result;
}
}
public string Error
{
get { return null; }
}
//.....
public string Numbers
{
get { return numbers; }
set { numbers = value; }
}
}
this works well but then the submit button won't be active unless i type one or more numbers in the txtNumbers textbox.I just want it to allow empty textbox.
How should i achieve that? Thanks for readking
You should make the entire pattern optional, not the separate parts, or it will think that ,02000 is a valid entry.
As each number has a specific length, you don't need to make the match non-greedy (using *?).
^((0[25]\d{3})(,0[25]\d{3})*)?$.
Allowing for whitespace around the numbers would be:
^\s*((0[25]\d{3})(\s*,\s*0[25]\d{3})*)?\s*$.
The regex is fine and does what you want. Perhaps your text box has some stray whitespace in it?
Try encapsulating stray whitespace at the beginning and end, which is more robust anyways:
^\s*(0(2|5)[0-9]{3})?((,0(2|5)[0-9]{3})*?)\s*$
Under your view model, just set Numbers =string.Empty when the view model starts loading, so through the change notification, your logic should be applied and you will gain the effect you want.
Cheers.