Trigger BorderBrush based on boolean value in ItemSource - image

I have a ItemsControl grid with a set of images. ItemSource is bound to a ObservableCollection that look like this:
public class Card
{
(...)
public string Image_url_small { get; set; }
public bool IsStarter = false;
(...)
}
On double click I have an event that changes IsStarter to true/false for every item with the same ID value - it works fine.
Now from the UI side I would like to highlight each of the items with IsStarter = true using a red border. Something like this:
So I wrote it like this:
<ItemsControl Name="DeckBox" ItemsSource="{Binding Lst}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<Border BorderThickness="2">
<Border.Style>
<Style TargetType="Border">
<Setter Property="BorderBrush" Value="Transparent"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsStarter}" Value="true">
<Setter Property="BorderBrush" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<Image Source="{Binding Image_url_small}"
MouseRightButtonUp="Image_MouseRightButtonUp"
MouseLeftButtonDown="Image_MouseLeftButtonDown"
Stretch="Fill"/>
</Border>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="10"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
My plan was to set a transparent border for all IsStarter = false and a red one for all with value = true but unfortunately it does not work as intended and they stay transparent all the time.
Any idea how to fix that? :)
Thx for help!

Related

Dynamic styling in .Net Maui/Xamarin

Let's say I want to display a calendar. Each day (DayViewModel) can have such attributes that should influence styling:
is day of current month
is weekend
is day available
And based on that I'd like to style my DayContentView like follows:
if day of current month -> opacity 1
if not current month -> opacity 0.5
if weekend -> background color is red
if available -> background color is green
Moreover the last one property ("is day available") can change based on user's action (clicked button "Check availability").
In other frameworks I'd create a few styles/style classes and assign them accordingly base on those flags. But since StyleClass is not bindable I cannot. Can I? Maybe there is some workaround.
Another option could be to create a separate style (using inheritance if needed) for all the combinations. But first of all the number of combinations raises like 2^n and the second issue is that I have no idea how to dynamically change whole styl for a view changing its name. Is it possible?
Finishing my quite long question: How to do it right/in a smart way? I don't want to store values of colors, font sizes, opacities etc. in a view model and bind all the values manually.
The same thing can be achieved using DataTriggers.
Result:
MainPage.xaml
<Window
x:Class="WpfApp6.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:WpfApp6"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="800"
Height="450"
mc:Ignorable="d">
<Window.DataContext>
<local:MainViewModel />
</Window.DataContext>
<ItemsControl Margin="5" ItemsSource="{Binding Days}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:DayModel}">
<Border
Padding="10"
Width="200"
Height="100"
Margin="5">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Background" Value="Gray" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsDayOfCurrentMonth}" Value="False">
<Setter Property="Opacity" Value="0.5" />
</DataTrigger>
<DataTrigger Binding="{Binding IsAvailable}" Value="True">
<Setter Property="Background" Value="Green" />
</DataTrigger>
<DataTrigger Binding="{Binding IsWeekend}" Value="True">
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<StackPanel>
<TextBlock Text="{Binding IsDayOfCurrentMonth, StringFormat='IsDayOfCurrentMonth: {0}'}" />
<TextBlock Text="{Binding IsAvailable, StringFormat='IsAvailable: {0}'}" />
<TextBlock Text="{Binding IsWeekend, StringFormat='IsWeekend: {0}'}" />
</StackPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Window>
DayModel.cs
public record DayModel(bool IsDayOfCurrentMonth, bool IsWeekend, bool IsAvailable);
MainViewModel.cs
public class MainViewModel
{
public ObservableCollection<DayModel> Days { get; } = new();
public MainViewModel()
{
//Create all possible cominations of days
for (int i = 0; i < 2; i++)
{
for (int j = 0; j < 2; j++)
{
for (int k = 0; k < 2; k++)
{
Days.Add(new DayModel(i == 0, j == 0, k == 0));
}
}
}
}
}
EDIT: sharing style by multiple controls
Here's how the same Style with Triggers might be shared by multiple controls (two of the three TextBlocks in this example):
<DataTemplate DataType="{x:Type local:DayModel}">
<Border
Width="200"
Height="100"
Margin="5"
Padding="10"
Background="#eee">
<StackPanel>
<StackPanel.Resources>
<Style x:Key="ColoredTextBlock" TargetType="TextBlock">
<Setter Property="Foreground" Value="Gray" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsDayOfCurrentMonth}" Value="False">
<Setter Property="Opacity" Value="0.5" />
</DataTrigger>
<DataTrigger Binding="{Binding IsAvailable}" Value="True">
<Setter Property="Foreground" Value="Green" />
</DataTrigger>
<DataTrigger Binding="{Binding IsWeekend}" Value="True">
<Setter Property="Foreground" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Resources>
<TextBlock Style="{StaticResource ColoredTextBlock}" Text="{Binding IsDayOfCurrentMonth, StringFormat='IsDayOfCurrentMonth: {0}'}" />
<TextBlock Style="{StaticResource ColoredTextBlock}" Text="{Binding IsAvailable, StringFormat='IsAvailable: {0}'}" />
<TextBlock Text="{Binding IsWeekend, StringFormat='IsWeekend: {0}'}" />
</StackPanel>
</Border>
</DataTemplate>
The result looks like this:
I planned to add an example as #ToolmakerStave suggested but I found a solution in the meantime that seems to be very nice so I decided to share it instead. The idea is as follows:
Declare view model that implements INotifyPropertyChanged
Create a XAML file and define there all the styles
In the code behind detect change of binding context (view model) and all its dynamically changing properties (IsAvailable) in my case
When a change is detected then create a style using styles defined in the XAML file applying them in the desired order (making updates)
Assign the final style to particular elements
So starting from my view model:
public class DayViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
// To style
public bool IsCurrentMonth { get; set; }
public bool IsWeekend { get; set; }
public bool IsHoliday { get; set; }
private bool _isAvailable;
public bool IsAvailable
{
get => _isAvailable;
set
{
_isAvailable = value;
PropertyChanged?.Invoke(
this,
new PropertyChangedEventArgs(nameof(IsAvailable))
);
}
}
// To display
public string DayOfWeek { get; set; }
public int DayOfMonth { get; set; }
}
Assuming that only IsAvailable property can change after some user action.
Then I have a XAML view that defines all the needed styles but they are not used directly in this file. I'll use them in the code behind. All the elements that I need to style have x:Name property set to get a reference to them from the code:
<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:vm="clr-namespace:MyProject.Calendar.ViewModels"
x:Class="MyProject.Calendar.ContentViews.DayView"
x:DataType="vm:DayViewModel">
<ContentView.Resources>
<ResourceDictionary>
<!-- By default Border has opacity 0.3 -->
<Style TargetType="Border">
<Setter Property="Opacity" Value="0.3" />
</Style>
<!-- These styles are used from the code behind -->
<Style x:Key="CurrentMonthBorder" TargetType="Border">
<Setter Property="Opacity" Value="1.0" />
</Style>
<Style x:Key="WeekendBorder" TargetType="Border">
<Setter Property="BackgroundColor" Value="LightGray" />
</Style>
<Style x:Key="AvailableBorder" TargetType="Border">
<Setter Property="BackgroundColor" Value="Green" />
</Style>
<Style x:Key="HolidayLabel" TargetType="Label">
<Setter Property="TextColor" Value="DarkRed" />
</Style>
<!-- // These styles are used from the code behind -->
</ResourceDictionary>
</ContentView.Resources>
<Border x:Name="DayBorder">
<VerticalStackLayout HorizontalOptions="FillAndExpand">
<Label x:Name="DayOfWeekLabel" Text="{Binding DayOfWeek}" />
<Label x:Name="DayOfMonthLabel" Text="{Binding DayOfMonth}" />
</VerticalStackLayout>
</Border>
</ContentView>
Now a helper class that allows to update a style with another style:
public static class StyleExtension
{
public static Style Update(this Style style, Style otherStyle)
{
var result = new Style(style.TargetType);
var allSetters = style.Setters.Concat(otherStyle.Setters);
foreach (var setter in allSetters)
{
result.Setters.Add(setter);
}
return result;
}
public static Style UpdateIf(this Style style, bool condition, Style otherStyle)
{
return style.Update(condition ? otherStyle : new Style(style.TargetType));
}
}
Final part, the code behind:
public partial class DayView : ContentView
{
private DayViewModel _viewModel;
public DayView()
{
InitializeComponent();
BindingContextChanged += OnBindingContextChanged;
}
private void OnBindingContextChanged(object sender, EventArgs e)
{
_viewModel = BindingContext as DayViewModel;
_viewModel.PropertyChanged += OnAvailabilityChanged;
StyleDayBorder();
StyleDayOfWeekLabel();
StyleDayOfMonthLabel();
}
private void OnAvailabilityChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(DayViewModel.IsAvailable))
{
StyleDayBorder();
}
}
private void StyleDayBorder()
{
DayBorder.Style = new Style(typeof(Border))
.UpdateIf(_viewModel.IsCurrentMonth, GetStyle("CurrentMonthBorder"))
.UpdateIf(_viewModel.IsWeekend, GetStyle("WeekendBorder"))
.UpdateIf(_viewModel.IsAvailable, GetStyle("AvailableBorder"));
}
private void StyleDayOfWeekLabel()
{
DayOfWeekLabel.Style = new Style(typeof(Label))
.UpdateIf(_viewModel.IsHoliday, GetStyle("HolidayLabel"));
}
private void StyleDayOfMonthLabel()
{
DayOfMonthLabel.Style = new Style(typeof(Label))
.UpdateIf(_viewModel.IsHoliday, GetStyle("HolidayLabel"));
}
private Style GetStyle(string name)
{
return Resources[name] as Style;
}
}
And final thought after the final code:) If there is no simpler solution then it would be nice to be able to do it in the XAML like this (pseudocode):
<Label Text="{Binding Message}">
<Label.Styles>
<Style ShouldApply="{Binding IsError}" Style="{StaticResource ErrorMessage}" />
<Style ShouldApply="{Binding IsWarning}" Style="{StaticResource WarningMessage}" />
...
</Label.Styles>
</Label>
And then all the styles would be created applying one after another in the defined order based on the defined conditions.

Windows Phone custom button click "area"

i have read a lot about this argument but i can't find a solution by myself about my specific problem.
I have create a custom button style in xaml (a circle with a letter inside) for a personal keyboard integrated into my app; the problem in that: the click event is lanched only when pressed the letter but i want make all button area sensible.
Here is my code:
<Style x:Key="ButtonStyle" TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid>
<Border CornerRadius="100"
BorderThickness="3"
BorderBrush="Black"
Margin="1"
Padding="0">
<Grid x:Name="ContentContainer">
<TextBlock Text="{TemplateBinding Content}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Button HorizontalAlignment="Left" VerticalAlignment="Top" Click="KeyboardButton_Click" Style="{StaticResource ButtonStyle}" Content="Q" MinWidth="40" MinHeight="40" />
private void KeyboardButton_Click(object sender, RoutedEventArgs e)
{
....
}
I think that you have no background set, so the button area is invisible for touch. Try to set "Background"="Transparent" in outside Border

Horizontal ListBox with HorizontalContentAlignment=Stretch

I have a horizontal listbox with 3 bindable items. How can I stretch them to all width of listbox? for example 1st element is at the left, 2nd element is at the middle and 3rd element on the right. Now I align them with margins, but I'm interested, is it possible without margins? I tried to set ListBox property HorizontalContentAlignment to Stretch, but not got what i want.
Thanks.
I tried your advice, but didn't get desirable result unfortunately. My XAML here:
<ListBox>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBoxItem Content="1"/>
<ListBoxItem Content="2"/>
<ListBoxItem Content="3"/>
</ListBox>
Here what I get:
and what I want is below:
Try with following approach:
<ListBox Grid.Row="1">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
</Style>
</ListBox.ItemContainerStyle>
<ListBoxItem HorizontalAlignment="Stretch">
<Grid>
<TextBlock Text="1" HorizontalAlignment="Left"/>
<TextBlock Text="2" HorizontalAlignment="Center"/>
<TextBlock Text="3" HorizontalAlignment="Right"/>
</Grid>
</ListBoxItem>
</ListBox>
That's a tricky one :) To do that, you need to change ListBox.ItemContainerStyle, check out the answer on MSDN forum.
Also, check out the following links on this site:
How to get a ListBox ItemTemplate to stretch horizontally the full width of the ListBox?
Silverlight 3: ListBox DataTemplate HorizontalAlignment
I'm not sure if you found a solution yet or not but this worked for me to get the items evenly spaced:
<ListBox>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="1"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBoxItem Content="Item1"/>
<ListBoxItem Content="Item2"/>
<ListBoxItem Content="Item3"/>
</ListBox>
Found at: https://stackoverflow.com/a/1270813/124721
Then mixing in the ItemContainerStyle I could get the content centered:
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
</Style>
</ListBox.ItemContainerStyle>
However I still wasn't able to get one item to aligned to the left, then another to the center, and the last one to the right. Applying the alignment to the item directly was shrinking the panel size. But maybe if it was wrapped in another container like a stack panel that would work.

How to wrap ItemsPanel in LongListSelector?

I am using listbox and wrappanel for displaying data.
For example:
<ListBox ItemTemplate="{StaticResource ItemTemplateListBoxAnimation}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<toolkit:WrapPanel ItemHeight="150" ItemWidth="150">
</toolkit:WrapPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
<DataTemplate x:Key="ItemTemplateListBoxAnimation">
<Grid Width="130" Height="130">
<Image Source="{Binding Image}"/>
</Grid>
</DataTemplate>
It's look like:
Now I need to use LongListSelector and grouping result:
<toolkit:LongListSelector ItemTemplate="{StaticResource ItemTemplateListBoxAnimation}">
<toolkit:LongListSelector.GroupItemsPanel>
<ItemsPanelTemplate>
<toolkit:WrapPanel/>
</ItemsPanelTemplate>
</toolkit:LongListSelector.GroupItemsPanel>
</toolkit:LongListSelector>
But it's look like:
I need to get:
Your assumptions?
Thank you
The issue is that the GroupItemsPanel property is not changing the ItemsPanel of the main list, but rather the ItemsPanel of the group headers, as can be seen here (image from http://www.windowsphonegeek.com/articles/wp7-longlistselector-in-depth--part2-data-binding-scenarios):
Unfortunately the WP toolkit doesn't seem to expose the ItemsPanel that you want, so you'll have to modify the toolkit source to get the behavior that you want.
Get the source from here: https://phone.codeplex.com/SourceControl/changeset/view/80797
Unzip, open up the Microsoft.Phone.Controls.Toolkit.WP7.sln solution in Visual Studio.
Under the Microsoft.Phone.Controls.Toolkit.WP7 project, open Themes/Generic.xaml
Scroll down to the Style that applies to LongListSelector (TargetType="controls:LongListSelector")
Change the TemplatedListBox.ItemsPanel to a WrapPanel
<primitives:TemplatedListBox.ItemsPanel>
<ItemsPanelTemplate>
<controls:WrapPanel/>
</ItemsPanelTemplate>
</primitives:TemplatedListBox.ItemsPanel>
Rebuild and reference the new dll, your items should wrap appropriately!
You can override it by using custom style
<toolkit:LongListSelector
Background="{StaticResource FCBackground}"
HorizontalContentAlignment="Stretch"
ItemsSource="{Binding NowShowingEvents}"
ItemTemplate="{StaticResource EventsListMediumItemTemplate}"
IsFlatList="True"
Style="{StaticResource LongListSelectorStyleCustom}"
>
</toolkit:LongListSelector>
<Style x:Key="LongListSelectorStyleCustom" TargetType="toolkit:LongListSelector">
<Setter Property="Background" Value="{StaticResource PhoneBackgroundBrush}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="toolkit:LongListSelector">
<toolkitPrimitives:TemplatedListBox x:Name="TemplatedListBox" Background="{TemplateBinding Background}">
<toolkitPrimitives:TemplatedListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</toolkitPrimitives:TemplatedListBox.ItemContainerStyle>
<toolkitPrimitives:TemplatedListBox.ItemsPanel>
<ItemsPanelTemplate>
<toolkit:WrapPanel Margin="24,0,12,24"/>
</ItemsPanelTemplate>
</toolkitPrimitives:TemplatedListBox.ItemsPanel>
</toolkitPrimitives:TemplatedListBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

How to Default Scroll Position in ScrollViewer/ListBox in WP7

I'm building a WP7 application using the Panorama control. I'm fairly new to WP7 so I might be missing something simple. My Panorama control is bound to ViewModel and PanoramaItems are added at runtime when a property of that ViewModel is populated with data via web service request. I'd like to have a section at the top of each PanoramaItem that holds a refresh button etc.
I'm able to add an item to the top of the ListBox using a Style but I would like to have that item scrolled off the top until a user pulls it down. Is there a way to set a default scroll position in the Style or in Template? I've read a few example of scrolling to a particular item once there is data using listBox.ScrollToItem, but that didn't work in my test application, or getting a handle on the ScrollViewer and using ScrollToVerticalOffset.
<phone:PhoneApplicationPage.Resources>
<Style x:Key="StoryStyle" TargetType="ListBox">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="{StaticResource PhoneForegroundBrush}"/>
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/>
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBox">
<ScrollViewer x:Name="ScrollViewer" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Foreground="{TemplateBinding Foreground}" Padding="{TemplateBinding Padding}">
<StackPanel>
<Button Content="Refresh"></Button>
<ItemsPresenter/>
</StackPanel>
</ScrollViewer>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</phone:PhoneApplicationPage.Resources>
<Grid x:Name="LayoutRoot" Background="Transparent">
<controls:Panorama ItemsSource="{Binding Programs}" x:Name="AllPrograms">
<controls:Panorama.Title>
<TextBlock></TextBlock>
</controls:Panorama.Title>
<controls:Panorama.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}" />
</DataTemplate>
</controls:Panorama.HeaderTemplate>
<controls:Panorama.ItemTemplate>
<DataTemplate>
<ListBox Margin="0,0,-12,0" ItemsSource="{Binding Stories}" Style="{StaticResource StoryStyle}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,17" Width="432" Height="80">
<TextBlock Text="{Binding SensibleTitle}" TextWrapping="Wrap" />
<TextBlock><Run Text="{Binding Duration, StringFormat='hh\\:mm\\:ss'}"></Run><Run Text=", "/><Run Text="{Binding DisplayDate, StringFormat='MMMM dd, yyyy'}"/></TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
</controls:Panorama.ItemTemplate>
</controls:Panorama>
</Grid>
Try changing SelectedIndex for ListBox.

Resources