Can anyone tell me how to center a polygon object within a given row/column of a Grid control?
The example that I have tried is taken from msdn.
<Grid x:Name="LayoutRoot" >
<Polygon Points="300,200 400,125 400,275 300,200"
Stroke="Purple"
StrokeThickness="2"
HorizontalAlignment="Center"
VerticalAlignment="Center" >
<Polygon.Fill>
<SolidColorBrush Color="Blue" Opacity="0.4" />
</Polygon.Fill>
</Polygon>
Cheers,
Xam
Add the attributes :-
HorizontalAlignment="Center" VerticalAlignment="Center"
to the Polygon.
Although a height and width are implied by the bounds of the polygon, it defaults to the size of the container.
If you just set
HorizontalAlignment="Center" VerticalAlignment="Center"
it will position the polygon's top-left in the centre.
You also have to explicitly set the height and width of the polygon to centre it and retain its bounds
Sample polygon Xaml with dimensions added:
<Grid x:Name="LayoutRoot">
<Path Data="M0.5,41.5 L201,0.5 L302,115 L157.25,157 z" Fill="#FFF4F4F5" Stroke="Black" UseLayoutRounding="False" HorizontalAlignment="Center" VerticalAlignment="Center" Width="302.5" Height="157.5"/>
</Grid>
Maybe this answer applies here too.
It uses a CenterConverter
public class CenterConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values[0] == DependencyProperty.UnsetValue || values[1] == DependencyProperty.UnsetValue)
{
return DependencyProperty.UnsetValue;
}
double width = (double) values[0];
double height = (double)values[1];
return new Thickness(-width/2, -height/2, 0, 0);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
And binds it in XAML like this
<Canvas>
<TextBlock x:Name="txt" Canvas.Left="40" Canvas.Top="40" TextAlignment="Center" Text="MMMMMM">
<TextBlock.Margin>
<MultiBinding Converter="{StaticResource centerConverter}">
<Binding ElementName="txt" Path="ActualWidth"/>
<Binding ElementName="txt" Path="ActualHeight"/>
</MultiBinding>
</TextBlock.Margin>
</TextBlock>
<Rectangle Canvas.Left="39" Canvas.Top="39" Width="2" Height="2" Fill="Red"/>
</Canvas>
To be able to use this in C# too and not only in XAML you need this class
public class Mover : DependencyObject
{
public static readonly DependencyProperty MoveToMiddleProperty =
DependencyProperty.RegisterAttached("MoveToMiddle", typeof (bool), typeof (Mover),
new PropertyMetadata(false, PropertyChangedCallback));
public static void SetMoveToMiddle(UIElement element, bool value)
{
element.SetValue(MoveToMiddleProperty, value);
}
public static bool GetMoveToMiddle(UIElement element)
{
return (bool) element.GetValue(MoveToMiddleProperty);
}
private static void PropertyChangedCallback(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
FrameworkElement element = sender as FrameworkElement;
if (element == null)
{
return;
}
if ((bool)e.NewValue)
{
MultiBinding multiBinding = new MultiBinding();
multiBinding.Converter = new CenterConverter();
multiBinding.Bindings.Add(new Binding("ActualWidth") {Source = element});
multiBinding.Bindings.Add(new Binding("ActualHeight") {Source = element});
element.SetBinding(FrameworkElement.MarginProperty, multiBinding);
}
else
{
element.ClearValue(FrameworkElement.MarginProperty);
}
}
}
Use it in XAML like so
<Canvas>
<TextBlock Canvas.Left="40" Canvas.Top="40" TextAlignment="Center" Text="MMMMMM"
local:Mover.MoveToMiddle="True"/>
</Canvas>
Or in C# like so
Mover.SetMoveToMiddle(UIElement, true);
Alternatively you can manipulate the RenderTransform
An alternative would be to bind to RenderTransform instead of Margin. In this case, the converter would return
return new TranslateTransform(-width / 2, -height / 2);
and the attached property's callback method would contain these lines:
if ((bool)e.NewValue)
{
...
element.SetBinding(UIElement.RenderTransformProperty, multiBinding);
}
else
{
element.ClearValue(UIElement.RenderTransformProperty);
}
This alternative has the advantage that the effect of the attached property is visible in the Visual Studio designer (which is not the case when setting the Margin property).
In XAML this would look like this:
<Canvas>
<TextBlock x:Name="txt" Canvas.Left="40" Canvas.Top="40" TextAlignment="Center" Text="MMMMMM">
<TextBlock.RenderTransform>
<MultiBinding Converter="{StaticResource centerConverter}">
<Binding ElementName="txt" Path="ActualWidth"/>
<Binding ElementName="txt" Path="ActualHeight"/>
</MultiBinding>
</TextBlock.RenderTransform>
</TextBlock>
<Rectangle Canvas.Left="39" Canvas.Top="39" Width="2" Height="2" Fill="Red"/>
</Canvas>
TextBlock was the control in question of the original answer. This way should be applicable to all objects of the class UIElement though.
Note: all credit goes to the original poster of the above linked answer
Related
Let's say we have 25 products A and 14 products B. I want to create chart that is representing them using rectangles and grid. I wrote this code below and it works, but the chart generated with it is very inaccurate. Any ideas how to fix it?
<StackPanel Orientation="Horizontal">
<!--Products A-->
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="0.2*"/>
<RowDefinition Height="percentage1*"/>
</Grid.RowDefinitions>
<Rectangle Fill="Red" HorizontalAlignment="Stretch" Grid.Row="1"/>
</Grid>
<!--Products B-->
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="0.2*"/>
<RowDefinition Height="percentage2*"/>
</Grid.RowDefinitions>
<Rectangle Fill="Red" HorizontalAlignment="Stretch" Grid.Row="1"/>
</Grid>
</StackPanel>
percentage1 = 25 products / (25 products + 14 products)
percentage2 = 14 products / (25 products + 14 products)
You should not control the size of the Rectangle with the width of a Grid row. Instead host the Rectangle elements in a ItemsControl and calculate the final rendered width based on the available space:
rectangle_width = ratio * available_width
= value / max_value_in_chart * available_width
It's best to move the logic and layout to a custom Control or UserControl. And because the Rectangle bars are hosted in an ItemsControl, you can as many bars as you need to without any hassle (opposed to modifying a Grid to add more rows):
Usage
<local:SimpleBarChart x:Name="BarChart"
BarThickness="64"
Orientation="Horizontal"/>
public MainWindow()
{
InitializeComponent();
// Better use data binding
this.BarChart.BarValues = new List<double> { 12, 24, 36 };
}
SimpleBarChart.xaml.cs
public partial class SimpleBarChart : UserControl
{
public IList<double> BarValues
{
get => (IList<double>)GetValue(BarValuesProperty);
set => SetValue(BarValuesProperty, value);
}
public static readonly DependencyProperty BarValuesProperty = DependencyProperty.Register(
"BarValues",
typeof(IList<double>),
typeof(SimpleBarChart),
new PropertyMetadata(default));
public double BarThickness
{
get => (double)GetValue(BarThicknessProperty);
set => SetValue(BarThicknessProperty, value);
}
public static readonly DependencyProperty BarThicknessProperty = DependencyProperty.Register(
"BarThickness",
typeof(double),
typeof(SimpleBarChart),
new PropertyMetadata(default));
public SimpleBarChart()
{
InitializeComponent();
}
}
SimpleBarChart.xaml
<UserControl>
<UserControl.Resources>
<local:BarValueToLengthConverter x:Key="BarValueToLengthConverter" />
</UserControl.Resources>
<ItemsControl ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=BarValues}"
HorizontalAlignment="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=HorizontalContentAlignment}"
HorizontalContentAlignment="Stretch"
VerticalAlignment="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=VerticalalContentAlignment}"
VerticalContentAlignment="Stretch">
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Margin"
Value="0,0,0,12" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Rectangle Fill="Orange"
Height="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=BarThickness}"
HorizontalAlignment="Left">
<Rectangle.Width>
<MultiBinding Converter="{StaticResource BarValueToLengthConverter}">
<Binding RelativeSource="{RelativeSource AncestorType=UserControl}"
Path="BarValues" />
<Binding />
<Binding RelativeSource="{RelativeSource AncestorType=UserControl}"
Path="BarThickness" />
<Binding RelativeSource="{RelativeSource AncestorType=ItemsControl}"
Path="ActualWidth" />
</MultiBinding>
</Rectangle.Width>
</Rectangle>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</UserControl>
BarValueToLengthConverter.cs
public class BarValueToLengthConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
IList<double>? barValues = values.OfType<IList<double>>().FirstOrDefault();
IList<double>? doubleValues = values.OfType<double>().ToList();
double barValue = doubleValues[0];
double barThickness = doubleValues[1];
double barHostWidth = doubleValues[2];
return barValue / barValues.Max() * barHostWidth;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
=> throw new NotSupportedException();
}
I have a list view that uses a collectionsource and wish to set the background colour of the ListViewItems depending upon whether their index in the list matches a value held in the view model. To do this I am binding the background of the listVeiwItem to the value in the ViewModel, and using a converter to determine the background colour. To make this determination the converter needs to be passed the ListViewItem's index. How do I obtain the index using XAML?
Here is the XAML for the data template used by the ListView:
<DataTemplate x:Key="ILMemberTemplate">
<StackPanel Orientation="Horizontal" Background="{Binding Path=ListIndex, Mode=OneWay, Converter={StaticResource ParticipantBackground}, ConverterParameter={???}}">
<TextBlock
Width="200"
TextAlignment="Left"
Foreground="{Binding Path=IsPC, Mode=OneWay, Converter={StaticResource ParticipantColour}}"
Text="{Binding Path=Name, Mode=OneWay}"/>
<TextBlock
Width="40"
TextAlignment="Center"
Foreground="{Binding Path=IsPC, Mode=OneWay, Converter={StaticResource ParticipantColour}}"
Text="{Binding Path=Initiative, Mode=OneWay}"/>
</StackPanel>
</DataTemplate>
You can't obtain the index in XAML and pass it as a ConverterParameter. This is not possible. But you can use a multi converter and bind to both the ListIndex and the parent ListViewItem container, e.g.:
class MultiConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var listIndex = values[0];
//...
ListViewItem item = value as ListViewItem;
ListView lv = FindParent<ListView>(item);
ICollectionView view = lv.ItemsSource as ICollectionView;
IEnumerator e = view.GetEnumerator();
int index = 0;
while (e.MoveNext())
{
if (Equals(item.DataContext, e.Current))
return index;
else
index++;
}
//return some brush based on the indexes..
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
private static T FindParent<T>(DependencyObject dependencyObject) where T : DependencyObject
{
var parent = VisualTreeHelper.GetParent(dependencyObject);
if (parent == null) return null;
var parentT = parent as T;
return parentT ?? FindParent<T>(parent);
}
}
XAML:
<DataTemplate x:Key="ILMemberTemplate">
<StackPanel Orientation="Horizontal">
<StackPanel.Background>
<MultiBinding Converter="{StaticResource multiConverter}">
<Binding Path="ListIndex" />
<Binding Path="." RelativeSource="{RelativeSource AncestorType=ListViewItem}" />
</MultiBinding>
</StackPanel.Background>
<!-- ... -->
</StackPanel>
</DataTemplate>
I have a IMultivalueConverter which updates the background color of a StackPanel when PropertyA or PropertyB is changed. These Controls are created dynamically.
Problem:
I have added two StackPanels and changed the PropertyA in the code when a button is clicked. This leads to a property changed event.
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
For the first stackpanel the background color is not updated, but for the second stackpanel this.PropertyChanged inturn calls my MultiValueConverter and background color is updated.
I am not able to understand why only one control is getting updated when both belong to same type and eventhandler is not null.
EDIT:
If I drag and drop a cell value from other control (DevExpress DataGrid) into the first stackpanel and then change the property, the background is not getting updated. It works fine until I drag and drop.
Update:
<StackPanel.Background>
<MultiBinding Converter="{StaticResource ResourceKey=BackgroundColorConverter}">
<Binding Path="PropertyA" UpdateSourceTrigger="PropertyChanged" />
<Binding Path="PropertyB" UpdateSourceTrigger="PropertyChanged" />
</MultiBinding>
</StackPanel.Background>
Update 2:
I have also tried using MultiDataTrigger instead of Converter, but couldn't solve the problem.
Unless i miss understood you, i don't see any complication in doing that,
<Window.Resources>
<app:BackgroundColorConverter x:Key="BackgroundColorConverter"/>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" >
<TextBox Text="{Binding PropertyA}" Width="200"/>
<TextBox Text="{Binding PropertyB}" Width="200"/>
</StackPanel>
<StackPanel Grid.Row="1" Margin="5">
<StackPanel.Background>
<MultiBinding Converter="{StaticResource ResourceKey=BackgroundColorConverter}">
<Binding Path="PropertyA" UpdateSourceTrigger="PropertyChanged" />
<Binding Path="PropertyB" UpdateSourceTrigger="PropertyChanged" />
</MultiBinding>
</StackPanel.Background>
</StackPanel>
<StackPanel Grid.Row="2" Margin="5">
<StackPanel.Background>
<MultiBinding Converter="{StaticResource ResourceKey=BackgroundColorConverter}">
<Binding Path="PropertyA" UpdateSourceTrigger="PropertyChanged" />
<Binding Path="PropertyB" UpdateSourceTrigger="PropertyChanged" />
</MultiBinding>
</StackPanel.Background>
</StackPanel>
</Grid>
the Converter :
public class BackgroundColorConverter:IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values==null)
{
return null;
}
return
new SolidColorBrush(Color.FromRgb(byte.Parse(values[0].ToString()), byte.Parse(values[1].ToString()),
50));
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
..And the Code behind
public partial class MainWindow : Window,INotifyPropertyChanged
{
private byte _propertyA ;
private byte _propertyB;
public byte PropertyA
{
get
{
return _propertyA;
}
set
{
if (_propertyA == value)
{
return;
}
_propertyA = value;
OnPropertyChanged();
}
}
public byte PropertyB
{
get
{
return _propertyB;
}
set
{
if (_propertyB == value)
{
return;
}
_propertyB = value;
OnPropertyChanged();
}
}
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
let me know if i did miss something
Reason:
When a value is dragged over the StackPanel, I am setting the BackgroundColor manually.
stackpanel.BackGround = new SolidColorBrush(Color.FromArgb(255,255,255,141));
Solution:
When I commented this line, the MultiValue converter is called and BackGround color is updated properly.
I created a property which changes according to DragEnter, DragOver and DragLeave events and then converter is called, I evaluate this value and set the Background color in the converter.
I have a strange behavior in my WPF application.
I am trying to launch an thickness animation on ScrollBar.ValueChanged event for animating an element margin, but every time the scroll bar value changed, the animation is binded to the OLD value of the scroll bar.
Resulting in a gap between the scroll bar value and the margin element.
<UserControl.Resources>
<Storyboard x:Key="AnimationScrollTest">
<ThicknessAnimation Storyboard.TargetName="ElementTest" Storyboard.TargetProperty="Margin" Duration="0:0:0:0.3"
To="{Binding ElementName=ScrollBarTest, Path=Value, Converter={StaticResource MyVerticalScrollBarValueToMarginConverter}}" />
</Storyboard>
<UserControl.Resources>
<Grid>
<ScrollBar x:Name="ScrollBarTest" Grid.RowSpan="3" Grid.ColumnSpan="2" Orientation="Vertical" Right" VerticalAlignment="Stretch" SmallChange="10" LargeChange="100" Value="0" Maximum="2000" >
<ScrollBar.Triggers>
<EventTrigger RoutedEvent="ScrollBar.ValueChanged" >
<BeginStoryboard Storyboard="{StaticResource AnimationScrollTest}" />
</EventTrigger>
</ScrollBar.Triggers>
</ScrollBar>
<Border x:Name="ElementTest" Grid.RowSpan="3" Grid.ColumnSpan="2" HorizontalAlignment="Center" VerticalAlignment="Top" Width="50" Height="50" Background="Red"></Border>
</Grid>
public class VerticalScrollBarValueToMarginConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return new Thickness(0, (Double)value, 0, 0);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
When the EventTrigger is linked to ScrollBar.Scroll event, it works well.
When a breakpoint is added in the converter, the value object in the Convert method have the correct value
If the ElementTest margin is directly binded to the ScrollBar value (without animation), it works well too.
Any idea??
Thanks a lot
PS: sorry for the bad english, I'm french!
I attempted to solve the previous value problem with attached properties
xaml
<Grid xmlns:l="clr-namespace:CSharpWPF">
<ScrollBar x:Name="ScrollBarTest"
Grid.RowSpan="3"
Grid.ColumnSpan="2"
Orientation="Vertical"
HorizontalAlignment="Right"
VerticalAlignment="Stretch"
SmallChange="10"
LargeChange="100"
Value="0"
Maximum="2000">
</ScrollBar>
<Border x:Name="ElementTest"
Grid.RowSpan="3"
Grid.ColumnSpan="2"
HorizontalAlignment="Center"
VerticalAlignment="Top"
Width="50"
Height="50"
Background="Red"
l:ScrollBarToMarginAnimator.ScrollBar="{Binding ElementName=ScrollBarTest}"></Border>
</Grid>
I have removed the storyboard from here and added an attached property l:ScrollBarToMarginAnimator.ScrollBar to the target element and binded to the scrollbar ScrollBarTest
class for the attached property
namespace CSharpWPF
{
public class ScrollBarToMarginAnimator : DependencyObject
{
public static ScrollBar GetScrollBar(DependencyObject obj)
{
return (ScrollBar)obj.GetValue(ScrollBarProperty);
}
public static void SetScrollBar(DependencyObject obj, ScrollBar value)
{
obj.SetValue(ScrollBarProperty, value);
}
// Using a DependencyProperty as the backing store for ScrollBar. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ScrollBarProperty =
DependencyProperty.RegisterAttached("ScrollBar", typeof(ScrollBar), typeof(ScrollBarToMarginAnimator), new PropertyMetadata(null, OnScrollBarChanged));
private static void OnScrollBarChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ScrollBar sb = e.NewValue as ScrollBar;
if (sb != null)
sb.Scroll += (ss, ee) =>
{
ThicknessAnimation ta = new ThicknessAnimation(new Thickness(0, sb.Value, 0, 0), TimeSpan.FromMilliseconds(300));
(d as FrameworkElement).BeginAnimation(FrameworkElement.MarginProperty, ta);
};
}
}
}
in this class I am listening to the Scroll event of the binded scrollbar and initiating a ThicknessAnimation on the attached element's Margin property.
above solution will listen any change in scroll bar and will react accordingly by animating margin of the border.
Multi Scrollbar
xaml
<Grid xmlns:l="clr-namespace:CSharpWPF">
<ScrollBar x:Name="vertical"
Grid.RowSpan="3"
Grid.ColumnSpan="2"
Orientation="Vertical"
HorizontalAlignment="Right"
VerticalAlignment="Stretch"
SmallChange="10"
LargeChange="100"
Value="0"
Maximum="2000"/>
<ScrollBar x:Name="horizontal"
Grid.RowSpan="3"
Grid.ColumnSpan="2"
Orientation="Horizontal"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
SmallChange="10"
LargeChange="100"
Value="0"
Maximum="2000"/>
<Border x:Name="ElementTest"
Grid.RowSpan="3"
Grid.ColumnSpan="2"
HorizontalAlignment="Center"
VerticalAlignment="Top"
Width="50"
Height="50"
Background="Red"
l:ScrollBarToMarginAnimator.Vertical="{Binding ElementName=vertical}"
l:ScrollBarToMarginAnimator.Horizontal="{Binding ElementName=horizontal}"/>
</Grid>
I have added another scrollbar and attached two properties to the border.
cs
namespace CSharpWPF
{
public class ScrollBarToMarginAnimator : DependencyObject
{
public static ScrollBar GetVertical(DependencyObject obj)
{
return (ScrollBar)obj.GetValue(VerticalProperty);
}
public static void SetVertical(DependencyObject obj, ScrollBar value)
{
obj.SetValue(VerticalProperty, value);
}
// Using a DependencyProperty as the backing store for Vertical. This enables animation, styling, binding, etc...
public static readonly DependencyProperty VerticalProperty =
DependencyProperty.RegisterAttached("Vertical", typeof(ScrollBar), typeof(ScrollBarToMarginAnimator),
new PropertyMetadata(null, (d, e) => AttachAnimation(d, e, true)));
public static ScrollBar GetHorizontal(DependencyObject obj)
{
return (ScrollBar)obj.GetValue(HorizontalProperty);
}
public static void SetHorizontal(DependencyObject obj, ScrollBar value)
{
obj.SetValue(HorizontalProperty, value);
}
// Using a DependencyProperty as the backing store for Horizontal. This enables animation, styling, binding, etc...
public static readonly DependencyProperty HorizontalProperty =
DependencyProperty.RegisterAttached("Horizontal", typeof(ScrollBar), typeof(ScrollBarToMarginAnimator),
new PropertyMetadata(null, (d, e) => AttachAnimation(d, e, false)));
private static void AttachAnimation(DependencyObject d, DependencyPropertyChangedEventArgs e, bool isVertical)
{
ScrollBar sb = e.NewValue as ScrollBar;
if (sb != null)
sb.Scroll += (ss, ee) =>
{
FrameworkElement fw = d as FrameworkElement;
Thickness newMargin = fw.Margin;
if (isVertical)
newMargin.Top = sb.Value;
else
newMargin.Left = sb.Value;
ThicknessAnimation ta = new ThicknessAnimation(newMargin, TimeSpan.FromMilliseconds(300));
fw.BeginAnimation(FrameworkElement.MarginProperty, ta);
};
}
}
}
I have created two properties and attached the animation using a flag
Starting with a Grouped Items Page template, I want to be able to perform tasks on the grid items when they are clicked. Namely, I want to change the background image, and add/remove the underlying object to a list of selected items.
Here's my DataTemplate:
<GridView.ItemTemplate>
<DataTemplate>
<Border BorderBrush="LightGray" BorderThickness="2" Margin="0,0,20,20">
<Grid HorizontalAlignment="Left" Width="390" Height="190">
<Grid.Background>
<ImageBrush ImageSource="/Assets/unselected.png" Stretch="None"/>
</Grid.Background>
<StackPanel Orientation="Horizontal" VerticalAlignment="Top">
<Image VerticalAlignment="Top" Stretch="None" Source="{Binding ImageUrl}" Margin="10,10,0,0"/>
<StackPanel MaxWidth="270">
<TextBlock Text="{Binding Summary}"/>
<TextBlock Text="{Binding Brand}" />
<TextBlock Text="{Binding Detail}" TextWrapping="Wrap" />
</StackPanel>
</StackPanel>
</Grid>
</Border>
</DataTemplate>
</GridView.ItemTemplate>
OnTap, I want to togle the ImageSource value of the Grid.Background from unselected.png to selected.png. This I believe I can do using VisualStates and Storyboards, but I've been unable to get this to work in the past (I'll spare you the chaos of my attempts in xaml).
Needless to say, I've tried following the steps detailed here using Blend, but the Grid.Background property doesn't seems to be state specific. If I try changing the background brush in the Pressed or Selected states, it also changes for the Normal state.
Since I want to grab the data context of the selected item and add/remove it from a list, should I just be handling all this together in an OnTap event handler? I would prefer to keep these concerns separated, but I'll do what I need to...
thanks!
One clean way to do this would be engage the selection method (Tap) in such a way that it only opperates on its items, and the items themselves have properties which Implement the INotifyPropertyChanged interface
Your View Model would have a collection of your custom objects that have properties that can notify the ui
public class MyObject : INotifyPropertyChanged
{
private string _summary;
public string summary
{
get {return _summary}
set
{
_summary = value;
OnPropertyChanged()
}
}
//Other Properties: brand || detail
private ImageSource _backgroundImage;
public ImageSource backgroundImage
{
get {return _backgroundImage}
set
{
_backgroundImage = value;
OnPropertyChanged()
}
}
private bool _IsSelected;
public bool IsSelected
{
get {return _IsSelected;}
set
{
_IsSelected = value;
OnPropertyChanged()
}
}
}
Then although your code behind can be used to change the value of IsSelected, or Background image ... if you choose to go with IsSelected, you can still separate your concerns by not directly setting the resource of the background image in code behind. The Codebehind will only iterate over the items to toggle the IsSelected property, and you can use xaml to define the image that the background should use by creating a custom converter.
public class MyCustomControlOrPage.cs : UserControl //Or ApplicationPage
{
//.......code
protected void HandleTap(object sender, GestureEventArgs e)
{
foreach(var item in ((Listbox)sender).ItemsSource)
{
((MyObject)item.IsSelected = (MyObject)item.Name == (e.NewItems[0] as MyObject).Name? true: false;
}
}
}
then the converter
public class BackgroundConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
ImageSource source = value == true ? new BitmapImage(uriForSelected) : new BitmapImage(uriForunselected);
return source;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
BitmapImage thisValue = value as BitmapImage;
return thisValue;
}
}
and FINALLY the XAML where the grid background binds to the IsSelected property and allows the converter to transform the bool to an ImageSource of type BitmapImage:
//add xmlns:Converters=clr-namesapce:Yournamespace.UpTo.TheNamespaceBackgroundConverterIsIn" to the page or control definition
<UserControl.Resources>
<Converters:BackgroundConverter x:key="BgSourceConverter" />
</UserControl.Resources>
<GridView.ItemTemplate>
<DataTemplate>
<Border BorderBrush="LightGray" BorderThickness="2" Margin="0,0,20,20">
<Grid HorizontalAlignment="Left" Width="390" Height="190">
<Grid.Background>
<ImageBrush ImageSource="{Binding Path=IsSelected, Mode=TwoWay, Converter={Binding Source={StaticResource BGSourceConverter}}}" Stretch="None"/>
</Grid.Background>
<StackPanel Orientation="Horizontal" VerticalAlignment="Top">
<Image VerticalAlignment="Top" Stretch="None" Source="{Binding ImageUrl}" Margin="10,10,0,0"/>
<StackPanel MaxWidth="270">
<TextBlock Text="{Binding Summary}"/>
<TextBlock Text="{Binding Brand}" />
<TextBlock Text="{Binding Detail}" TextWrapping="Wrap" />
</StackPanel>
</StackPanel>
</Grid>
</Border>
</DataTemplate>
</GridView.ItemTemplate>