uwp define eventriggerbehavior inside style - xaml

I'm currently stuck with a problem. I want to declare a eventriggerbehavior for all my listviews. this is my code:
<Style TargetType="ListView">
<Setter Property="ItemTemplate" Value="{StaticResource itemShowTemplate}" />
<i:Interaction.Behaviors>
<Interactions:EventTriggerBehavior EventName="ItemClicked">
<Interactions:InvokeCommandAction Command="{Binding ShowItemClickedCommand}" />
</Interactions:EventTriggerBehavior>
</i:Interaction.Behaviors>
</Style>
the problem I have now is that EventTriggerBehavior is looking for the event on Style and not the targettype of Listview. And the only property left to set on EventTriggerBehavior is SourceObject. But I don't want this behavior on 1 listview, I want it on al my listviews.
Is there a way todo this?

My first idea was that it could work this way:
<Style TargetType="Button">
<Setter Property="i:Interaction.Behaviors">
<Setter.Value>
<i:BehaviorCollection>
<core:EventTriggerBehavior EventName="Click">
<core:InvokeCommandAction Command="{Binding TestCommand}" />
</core:EventTriggerBehavior>
</i:BehaviorCollection>
</Setter.Value>
</Setter>
</Style>
But unfortunately this works only for the first control which gets the behavior attached. The reason is that the value is constructed just once and the AssociatedObject property of the BehaviorCollection is set to the first control.
I have then come over this solution - How to add a Blend Behavior in a Style Setter .
The idea is to create an attached property that manually assigns the behavior to the control.
Based on this I suggest you do it like this:
public static class ListViewBehaviorAttacher
{
public static readonly DependencyProperty IsAttachedProperty = DependencyProperty.RegisterAttached(
"IsAttached", typeof(bool), typeof(ListViewBehaviorAttacher), new PropertyMetadata(default(bool), IsAttachedChanged));
private static void IsAttachedChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
var listView = (ListView)dependencyObject;
//create the binding
BehaviorCollection collection = new BehaviorCollection();
var eventTrigger = new EventTriggerBehavior() { EventName = "ItemClick" };
var invokeCommandAction = new InvokeCommandAction();
//binding to command
BindingOperations.SetBinding(
invokeCommandAction,
InvokeCommandAction.CommandProperty,
new Binding() { Path = new PropertyPath("ShowItemClickedCommand"), Source = listView.DataContext });
eventTrigger.Actions.Add(invokeCommandAction);
collection.Add(eventTrigger);
listView.SetValue(Interaction.BehaviorsProperty, collection);
}
public static void SetIsAttached(DependencyObject element, bool value)
{
element.SetValue(IsAttachedProperty, value);
}
public static bool GetIsAttached(DependencyObject element)
{
return (bool)element.GetValue(IsAttachedProperty);
}
}
And then in the style attach it like this:
<Style TargetType="ListView">
<Setter Property="SelectionMode" Value="None"></Setter>
<Setter Property="IsItemClickEnabled" Value="True" />
<Setter Property="local:ListViewBehaviorAttacher.IsAttached" Value="True" />
</Style>

Related

How to trigger a trigger

My question relates to how to initiate as action based on user mouse input.
I have a wpf window that displays information on an organization. Some organizations are vendors in which case vendor information is displayed in another row of the grid. I use the following trigger to display/hide that row. This works as desired.
<RowDefinition>
<RowDefinition.Style>
<Style
TargetType="RowDefinition">
<Setter Property="Height" Value="0" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsVendor}" Value="True">
<Setter Property="Height" Value="*" />
</DataTrigger>
</Style.Triggers>
</Style>
</RowDefinition.Style>
</RowDefinition>
For an organization which is not a vendor, the user can click on a button that adds vendor information and links that new information to the organization. However, that action does not cause the trigger to fire. Is there a way to do that? Or is there a different approach that would work better?
In your case, I will use a converter and bind the value of Height property.
<RowDefinition>
<RowDefinition.Style>
<Style TargetType="RowDefinition">
<Setter Property="Height" Value="{Binding IsVendor, Converter={StaticResource IsVendorConverter}}" />
</Style>
</RowDefinition.Style>
</RowDefinition>
The converter will receive your boolean value (IsVendor) and returns "0" if IsVendor is false and "*" if IsVendor is true.
public class IsVendorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var isVendor = (bool)value;
if (isVendor)
{
return "*";
}
return "0";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
You didn't share your xaml, but here is a sample used with a button (clicking one button changes the height of the other one).
<Window
x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfApp1"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006">
<Window.Resources>
<local:IsVendorConverter x:Key="IsVendorConverter" />
</Window.Resources>
<StackPanel Orientation="Horizontal">
<Button Width="200" Height="{Binding IsVendor, Converter={StaticResource IsVendorConverter}}" />
<Button Width="200" Height="100" Click="Button_Click"/>
</StackPanel>
</Window>
Code Behind:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
private bool _isVendor = false;
public bool IsVendor {
get { return _isVendor; }
set { _isVendor = value; OnPropertyRaised("IsVendor"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyRaised(string propertyname)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyname));
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
IsVendor = true;
}
}

Xamarin forms IOS Buttons looking distorted

I have the following XAML code below :
<StackLayout
Grid.Row="2"
Orientation="Horizontal"
VerticalOptions="End"
Margin="0,0,0,20"
Spacing="28">
<Button
x:Name="SignInButton"
Visual="Material"
Padding="5"
Margin="10,0,0,0"
Style="{DynamicResource ButtonSecondary}"
HorizontalOptions="FillAndExpand"
Text="Sign In"
Clicked="SignInButton_Clicked"/>
<Button
x:Name="JoinUsButton"
Visual="Material"
Padding="5"
Margin="0,0,10,0"
Style="{DynamicResource ButtonPrimary}"
HorizontalOptions="FillAndExpand"
VerticalOptions="End"
Text="Join Us"
Clicked="JoinUsButton_Clicked"/>
</StackLayout>
The dynamic resources currently stored in the App.xaml file are as follows :
<Style x:Name="ButtonSecondary" x:Key="ButtonSecondary" TargetType="Button" ApplyToDerivedTypes="True">
<Setter Property="BackgroundColor"
Value="{DynamicResource SecondaryColor}" />
<Setter Property="TextColor"
Value="{DynamicResource PrimaryTextColor}" />
<Setter Property="BorderWidth"
Value="1" />
<Setter Property="BorderColor"
Value="{DynamicResource SecondaryBorderColor}" />
<Setter Property="CornerRadius"
Value="50" />
</Style>
However, when I run the app on iOS the buttons look like the image below.
However, on the android device, the buttons look like the image below :
Cauuse : In iOS , if you want to achieve the effect like the above image which you get in Android , you need to set the CornerRadius as half of its HeightRequest .
Solution
Option 1
If the size of the button is always a fixed value , you just need to set the HeightRequest in the style
<Style x:Name="ButtonSecondary" x:Key="ButtonSecondary" TargetType="Button" ApplyToDerivedTypes="True">
<Setter Property="BackgroundColor"
Value="{DynamicResource SecondaryColor}" />
<Setter Property="TextColor"
Value="{DynamicResource PrimaryTextColor}" />
<Setter Property="BorderWidth"
Value="1" />
<Setter Property="BorderColor"
Value="{DynamicResource SecondaryBorderColor}" />
<Setter Property="CornerRadius"
Value="25" />
<Setter Property="HeightRequest"
Value="50" /> // double of CornerRadius
</Style>
Option 2 :
If the size of Button will change in runtime , you could use Custom Renderer to set the CornerRadius in iOS platform .
in Forms
create a custom button
public class MyButton:Button
{
}
in iOS
using Foundation;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using App6;
using App6.iOS;
using System.ComponentModel;
[assembly:ExportRenderer(typeof(MyButton),typeof(MyButtonRenderer))]
namespace App6.iOS
{
public class MyButtonRenderer:ButtonRenderer
{
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if(e.PropertyName=="Height")
{
var height = Element.Height;
Control.Layer.MasksToBounds = true;
Control.Layer.BorderColor = UIColor.Black.CGColor;
Control.Layer.CornerRadius = (nfloat)(height / 2.0);
Control.Layer.BorderWidth = (nfloat)0.5;
}
}
}
}
in xaml
<local:MyButton
x:Name="SignInButton"
Visual="Material"
Padding="5"
Margin="10,0,0,0"
Style="{DynamicResource ButtonSecondary}"
HorizontalOptions="FillAndExpand"
Text="Sign In"
/>
While I can't see the exact issue you are seeing with the distortion I do see an inconsistency between platforms. This ultimately comes down to how the individual platforms render the CornerRadius property. Android will limit it to what is visibly sensible (basically half the height/width, whichever is smaller) whereas iOS will just do as you ask.
This image shows on the left what I currently see, the middle is my second solution and the right is my first solution.
My possible solutions are:
Attach a Behavior
public class RoundCornerBehavior : Behavior<Button>
{
protected override void OnAttachedTo(Button button)
{
button.SizeChanged += OnSizeChanged;
base.OnAttachedTo(button);
}
protected override void OnDetachingFrom(Button button)
{
button.SizeChanged -= OnSizeChanged;
base.OnDetachingFrom(button);
}
private void OnSizeChanged(object sender, EventArgs e)
{
var button = (Button) sender;
button.CornerRadius = (int)Math.Min(button.Width, button.Height) / 2;
}
public static readonly BindableProperty AttachBehaviorProperty =
BindableProperty.CreateAttached("AttachBehavior", typeof(bool), typeof(RoundCornerBehavior), false, propertyChanged: OnAttachBehaviorChanged);
public static bool GetAttachBehavior(BindableObject view)
{
return (bool)view.GetValue(AttachBehaviorProperty);
}
public static void SetAttachBehavior(BindableObject view, bool value)
{
view.SetValue(AttachBehaviorProperty, value);
}
static void OnAttachBehaviorChanged(BindableObject view, object oldValue, object newValue)
{
if (!(view is Button button))
{
return;
}
var attachBehavior = (bool)newValue;
if (attachBehavior)
{
button.Behaviors.Add(new RoundCornerBehavior());
}
else
{
var toRemove = button.Behaviors.FirstOrDefault(b => b is RoundCornerBehavior);
if (toRemove != null)
{
button.Behaviors.Remove(toRemove);
}
}
}
}
Then simply attach in your style:
<Setter Property="roundButton:RoundCornerBehavior.AttachBehavior" Value="true" />
I would suggest writing some kind of Behavior to provide the sensible CornerRadius which would essentially take the Width and Height properties of the control and simply set the CornerRadius to half the smallest value. I will see if I can knock something up to provide a concrete example shortly.
The nice result of this approach will allow you to continue to define the controls as you were previously and keep the logic self contained in the attached behavior.
Sub class button
An alternative would be to sub class Button and created your own RoundedButton that could do the same as the Behavior approach. Then
public class RoundedButton : Button
{
protected override void OnSizeAllocated(double width, double height)
{
base.OnSizeAllocated(width, height);
this.CornerRadius = (int)Math.Min(width, height) / 2;
}
}

How to access a style inside user control in UWP

I have a user control like this:
<UserControl>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<HyperlinkButton Grid.Row="0" />
<TextBlock Name="textblock" Grid.Row="1"
Text="{Binding dailyText, ElementName=userControl}">
</TextBlock>
</Grid>
</UserControl>
Nevertheless, I don't know, how can I set a style from mainwindow to user control? I have solved the problem to access to other properties like this:
public static readonly DependencyProperty MyContentProperty =
DependencyProperty.Register("MyContent", typeof(object), typeof(Day), null);
public object MyContent
{
get { return (object)GetValue(MyContentProperty ); }
set { SetValue(MyContentProperty , value); }
}
And then
<local:Day MyContent="Hello World" />
However, it doesn't work the style. There is no change in the sytle.
Thank you.
(Modification)
Below is a mainWindow part.
<Page.Resources>
<Style TargetType="TextBlock" x:Name="MyTextBlockStyle">
<Setter Property="Foreground" Value="Blue" />
<Setter Property="SelectionHighlightColor" Value="Red"/>
<Setter Property="FontSize" Value="10"/>
</Style>
</Page.Resources>
<local:Day MyStyle="{StaticResource MyTextBlockStyle}">
Behind-code part in userControl
public static readonly DependencyProperty MyStyleProperty =
DependencyProperty.Register("MyStyle", typeof(Style), typeof(Day), null);
public Style MyStyle
{
get { return (Style)GetValue(MyStyleProperty); }
set { SetValue(MyStyleProperty, value); }
}
You can use PropertyMetadata to initialise and set Style to your TextBlock. Like,
public Style MyStyle
{
get { return (Style)GetValue(MyStyleProperty); }
set { SetValue(MyStyleProperty, value); }
}
public static readonly DependencyProperty MyStyleProperty =
DependencyProperty.Register("MyStyle", typeof(Style), typeof(Day),
new PropertyMetadata(null, OnStyleChange));
private static void OnStyleChange(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = d as Day;
control.textblock.Style = (Style)e.NewValue
}

Universal Apps: How to bind a property of a ListViewItem (container) to the actual item (View Model)?

I have this ListView
<ListView ItemsSource="{Binding Items}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Background" Value="{Binding IsValid, Converter={StaticResource BooleanToBrushConverter}" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I know bindings don't work for Setters in Universal Applications but, then how do I bind a the container of an item with the item itself? What's the point of creating a custom container if you cannot provide any logic, but constant values?
You need to be careful with backgrounds in the UWP ListViewItem, as there is a lot of complex theming around this including different kinds of pressed and drag backgrounds
I think an easier way to achieve this is to change the content alignments in the ListViewItem, and then add a grid to your item template from which you can add your background
<ListView ItemsSource="{Binding Items}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Grid Background="{Binding Path=IsValid, Converter={StaticResource BooleanToBrushConverter}}">
<TextBlock Text="{Binding Name}" VerticalAlignment="Center" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
OK, you can't bind to the DataContext using the normal ways, but you can do it using other (smart) methods:
See this post. It provides a "helper" that allows to bind to the DataContext with a Setter.
This is pretty easy, really.
I would guess you are trying to use the variable sized grid view? That's a pretty common request, actually. But what you are asking for is tricky because of the various scopes and how things are rendered.
The first think you will need to do is override ListView with your own custom ListView. Let's call it MyListView. Like this:
public class MyItem
{
public int RowSpan { get; set; }
public int ColSpan { get; set; }
}
public class MyListView : ListView
{
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
var model = item as MyItem;
try
{
element.SetValue(VariableSizedWrapGrid.ColumnSpanProperty, model.ColSpan);
element.SetValue(VariableSizedWrapGrid.RowSpanProperty, model.RowSpan);
}
catch
{
element.SetValue(VariableSizedWrapGrid.ColumnSpanProperty, 1);
element.SetValue(VariableSizedWrapGrid.RowSpanProperty, 1);
}
finally
{
element.SetValue(VerticalContentAlignmentProperty, VerticalAlignment.Stretch);
element.SetValue(HorizontalContentAlignmentProperty, HorizontalAlignment.Stretch);
base.PrepareContainerForItemOverride(element, item);
}
}
}
Everything takes place in PrepareContainerForItemOverride and it's the only method you need override in the subclass. Please also notice that I have not set them to a binding. This is because these properties are only observed when the item is rendered. If you want to refresh your ListView and re-render your items based on new values, you need to call InvalidateMeasure() on the root panel, which is tricky. You can do it like this:
// MyListView
public void Update()
{
if (!(this.ItemsPanelRoot is VariableSizedWrapGrid))
throw new ArgumentException("ItemsPanel is not VariableSizedWrapGrid");
foreach (var container in this.ItemsPanelRoot.Children.Cast<GridViewItem>())
{
var model = item as MyItem;
VariableSizedWrapGrid.SetRowSpan(container, data.RowSpan);
VariableSizedWrapGrid.SetColumnSpan(container, data.ColSpan);
}
this.ItemsPanelRoot.InvalidateMeasure();
}
If this makes sense, you can see the entire implementation here.
Best of luck!

Set background color depending on data-bound value

I've seen some answers before but nothing really helped me out.
I also have a class DecideModel (This will be a dataset retrieved from DB, but for purpose of this question, I have added an ObservableCollection) which contains
static DecideModel()
{
All = new ObservableCollection<DecideModel>
{
new DecideModel
{
DatePerformed = new DateTime(2015, 4, 06),
Result = "Maybe"
},
new DecideModel
{
DatePerformed = new DateTime(2015, 4, 05),
Result = "No"
},
new DecideModel
{
DatePerformed = new DateTime(2015, 4, 04),
Result = "Yes"
}
};
}
public DateTime DatePerformed { set; get; }
public string Result { set; get; }
public static IList<DecideModel> All { set; get; }
}
In my XAML code I have
<ContentPage.Resources>
<ResourceDictionary>
<Color x:Key="Maybe">#ffddbc21</Color>
<Color x:Key="Yes">#3CB371</Color>
<Color x:Key="No">#B22222</Color>
<Color x:Key="Depends">#ffd78800</Color>
</ResourceDictionary>
</ContentPage.Resources>
<Label Text="{Binding Result}" HorizontalOptions="FillAndExpand" BackgroundColor="{StaticResource {BindingSource Result}}" />
I am trying to dynamically set the background color of the label with respect to what result I have obtained from the Object.
Please let me know if you have any idea on how to do it. I am looking for any useful option available.
What you probably need is a ValueConverter. What you are doing now is setting the background color to 'Maybe', 'No' or 'Yes', which clearly isn't a color.
What you need to do is convert that value to a color. You can do it like this.
Create a new class that implements the IValueConverter interface. It will probably look something like this:
public class YesNoMaybeToColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
switch(value.ToString().ToLower())
{
case "yes":
return Color.Green;
case "no":
return Color.Red;
case "maybe":
return Color.Orange;
}
return Color.Gray;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
// You probably don't need this, this is used to convert the other way around
// so from color to yes no or maybe
throw new NotImplementedException();
}
}
Then add this class as a static resource to your XAML page like this:
<ContentPage.Resources>
<!-- Add this line below -->
<local:YesNoToBooleanConverter x:Key="YesNoMaybeToColorConverter" />
<!-- You can remove the underneath -->
<!--<ResourceDictionary>
<Color x:Key="Maybe">#ffddbc21</Color>
<Color x:Key="Yes">#3CB371</Color>
<Color x:Key="No">#B22222</Color>
<Color x:Key="Depends">#ffd78800</Color>
</ResourceDictionary>-->
</ContentPage.Resources>
Now in your binding you have to tell him what converter to use. Do it like this:
<Label Text="{Binding Result}" HorizontalOptions="FillAndExpand" BackgroundColor="{Binding Result, Converter={StaticResource YesNoMaybeToColorConverter}}" />
It should now see the value in the Result field, put it through the converter you have defined and return the color that you corresponded to that value.
For a pure XAML way of achieving this without much overhead, you can use a DataTrigger. Note that you can add as many setters per trigger as needed, making this slightly more flexible than the previously suggested solutions, it's also keeping view logic in the view where it should be.
<Label Text="{Binding Result}" HorizontalOptions="FillAndExpand">
<Label.Triggers>
<DataTrigger TargetType="Label" Binding="{Binding Result}" Value="Yes">
<Setter Property="BackgroundColor" Value="#3CB371" />
</DataTrigger>
<DataTrigger TargetType="Label" Binding="{Binding Result}" Value="No">
<Setter Property="BackgroundColor" Value="#B22222" />
</DataTrigger>
<DataTrigger TargetType="Label" Binding="{Binding Result}" Value="Maybe">
<Setter Property="BackgroundColor" Value="#ddbc21" />
</DataTrigger>
<DataTrigger TargetType="Label" Binding="{Binding Result}" Value="Depends">
<Setter Property="BackgroundColor" Value="#d78800" />
</DataTrigger>
</Label.Triggers>
</Label>
Note that you could probably cut out one of the triggers by setting the BackgroundColor property to a sensible default (likely "Depends" in this case).
I found 2 another options for managing this, because sometimes we need to change not only color but font and other values.
At first you need to add name to your control like this:
<Label x:Name="MyLabel" Text="{Binding Result}" HorizontalOptions="FillAndExpand"/>
1. You can set color and other properties in code behind like this, this way needs some time for updating, so it's not the best choice, when Text property was just updated.
protected override void OnAppearing()
{
if (this.MyLabel.Text.Contains("yes")
{
this.MyLabel.TextColor = Color.FromHex("#98ee99");
}
else
{
this.MyLabel.TextColor = Color.FromHex("#ff867c");
}
}
2. Another way is using Prism EventAggregator, I guess in Xamarin.Forms it is Messaging Center. Here is one good example with Prism.
In this case you should send Event with your value from whatever place in your project, for example when it's should be already updated.
_evenAggregator.GetEvent<MyEvent>().Publish(Result);
Then you should subscribe the Event in place where you need to receive fresh value. In this case it should be OnAppearing method in code behind class.
void UpdateColor(string result)
{
if(result.Contains("yes")
{
this.MyLabel.TextColor = Color.FromHex("#98ee99");
}
else
{
this.MyLabel.TextColor = Color.FromHex("#ff867c");
}
}
protected override void OnAppearing()
{
base.OnAppearing();
_eventAggregator.GetEvent<MyEvent>().Subscribe(UpdateColor);
}