Images in a List in WPF MVVM - wpf

I have a question regarding how to best accomplish something in WPF MVVM. I have in my ViewModel a series of integers. For the sake of example, lets call them:
public int Yellow
{
get;set;
}
public int Red
{
get;set;
}
public int Green
{
get;set;
}
I also have some small images that are very simple: A Red circle, a Yellow circle, and a Green circle. The idea is to have an area on the view with a number of these images, based on the above properties. So if this instance of the view model has 3 Yellow, 2 Red, and 1 Green, I want 6 images in my ListBox, 3 of the yellow circle, 2 of the red, and 1 of the green. Right now, I have it working, but using some very clumsy code where I build the image list in the ViewModel using an ugly for-loop. Is there some more elegant way to accomplish this task in WPF? Ideally, I wouldn't want to have to reference the image in the ViewModel at all...

You could use an ImageBrush to tile a rectangle with an image, and bind the width of the rectangle to the number of copies of the image you want. Something like this:
<StackPanel Orientation="Horizontal">
<StackPanel.LayoutTransform>
<ScaleTransform ScaleX="20" ScaleY="20"/>
</StackPanel.LayoutTransform>
<Rectangle Width="{Binding Yellow}" Height="1">
<Rectangle.Fill>
<ImageBrush
ImageSource="Yellow.png"
Viewport="0,0,1,1"
ViewportUnits="Absolute"
TileMode="Tile"/>
</Rectangle.Fill>
</Rectangle>
<Rectangle Width="{Binding Red}" Height="1">
<Rectangle.Fill>
<ImageBrush
ImageSource="Red.png"
Viewport="0,0,1,1"
ViewportUnits="Absolute"
TileMode="Tile"/>
</Rectangle.Fill>
</Rectangle>
<Rectangle Width="{Binding Green}" Height="1">
<Rectangle.Fill>
<ImageBrush
ImageSource="Green.png"
Viewport="0,0,1,1"
ViewportUnits="Absolute"
TileMode="Tile"/>
</Rectangle.Fill>
</Rectangle>
</StackPanel>
Update: As Ray pointed out in his comment, if you are just trying to draw circles then you will get better zoom behavior by using a DrawingBrush than by using an Image:
<StackPanel Orientation="Horizontal">
<StackPanel.LayoutTransform>
<ScaleTransform ScaleX="20" ScaleY="20"/>
</StackPanel.LayoutTransform>
<StackPanel.Resources>
<EllipseGeometry x:Key="Circle" RadiusX="1" RadiusY="1"/>
</StackPanel.Resources>
<Rectangle Width="{Binding Yellow}" Height="1">
<Rectangle.Fill>
<DrawingBrush ViewportUnits="Absolute" TileMode="Tile">
<DrawingBrush.Drawing>
<GeometryDrawing
Brush="Yellow"
Geometry="{StaticResource Circle}"/>
</DrawingBrush.Drawing>
</DrawingBrush>
</Rectangle.Fill>
</Rectangle>
<!-- etc. -->

A possibility would be to use a ValueConverter. It is very flexible, decoupled and helps to let the xaml simple. Here the code for such a value-converter:
public class ImageCountValueConverter : IValueConverter{
public string ImagePath {
get;
set;
}
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
if(null == value){
return Enumerable.Empty<string>();
} else if (value is int) {
List<string> list = new List<string>();
int v = (int)value;
for (int i = 0; i < v; i++) {
if (parameter is string) {
list.Add((string)parameter);
} else {
list.Add(ImagePath);
}
}
return list;
} else {
Type t = value.GetType();
throw new NotSupportedException("The \"" + t.Name+ "\" type is not supported");
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
throw new NotImplementedException();
}
}
The markup would look like this:
<StackPanel>
<ItemsControl ItemsSource="{Binding Yellow,Converter={StaticResource ImageCount_ValueConverter},ConverterParameter=/image/yellow.png}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<Image Source="{Binding}" Stretch="None"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ItemsControl ItemsSource="{Binding Red,Converter={StaticResource ImageCount_ValueConverter},ConverterParameter=/image/red.png}" >
...
The declaration would look something like:
<Window.Resources>
<local:ImageCountValueConverter x:Key="ImageCount_ValueConverter" ImagePath="/image/sampleImage.png"/>
</Window.Resources>
Options
Depending on your requirements you can also extend it or change it to work with ImageSource instead of strings or even provide a List<Brush> as output and then use a shape in your DataTemplate where the Brush is set through the Binding.

Related

Handling GridItems click

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>

Changing color of a Chart Dynamically in WPF using MVVM and Databinding

I'm trying to build a Chart, with Columns. The columns will have different colors by value.
I'm using MVVM with WPF and Databinding.
How i'm trying to do (Into my ViewModel):
private Int16 colorValue;
public Int16 ColorValue
{
get { return colorValue; }
set
{
colorValue = value;
if (ColorValue < 20)
ColorType = new SolidColorBrush { Color = Colors.Aqua };
if (ColorValue < 40)
ColorType = new SolidColorBrush { Color = Colors.Gray };
if (ColorValue >= 41)
ColorType = new SolidColorBrush { Color = Colors.Black };
}
}
private Brush colorType;
public Brush ColorType
{
get { return colorType; }
set
{
if (value != null)
{
colorType = value;
OnPropertyChanged("ColorType");
}
}
}
Into My Xaml (This is the Static Resource to change the Column Color Attribute):
<Style x:Key="ColorByGradeColumn" TargetType="DVC:ColumnDataPoint">
<Setter Property="Background" Value="DarkGray"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate
TargetType="DVC:ColumnDataPoint">
<Border
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid Background="{Binding ColorType}">
<Rectangle>
<Rectangle.Fill>
<LinearGradientBrush>
<GradientStop Color="#77ffffff" Offset="0"/>
<GradientStop Color="#00ffffff" Offset="1"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<Border BorderBrush="#ccffffff" BorderThickness="1">
<Border BorderBrush="#77ffffff" BorderThickness="1"/>
</Border>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
My Chart into Xaml:
<Grid Grid.Column="2" Height="368" HorizontalAlignment="Left" Name="grid1" VerticalAlignment="Bottom" Width="1009" Grid.ColumnSpan="4" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="198*" />
<ColumnDefinition Width="191*" />
</Grid.ColumnDefinitions>
<DVC:Chart x:Name="ColumnChart"
Grid.ColumnSpan="2">
<DVC:ColumnSeries
AnimationSequence="FirstToLast"
FlowDirection="LeftToRight"
Title="Largura"
ItemsSource="{Binding Path=Placas, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ToolTip="{Binding Path=Slab.InfThick}"
DependentValueBinding="{Binding Path=Slab.InfThick}"
IndependentValueBinding="{Binding Path=Slab.SlabId}"
DataPointStyle="{StaticResource ColorByGradeColumn}">
</DVC:ColumnSeries>
</DVC:Chart>
</Grid>
So... My Chart using ColumnSeries get it's attribute by the Static Resource defined into DataPointStyle. StaticResource 'ColorByGradeColumn' i've made a binding to my property ColorType.
Here's the question... Why isn't working? I've followed the steps explained in this link:
Columns of a different color [Customizing the appearance of Silverlight charts with re-templating and MVVM]
And I really don't know what i'm missing.
Thanks in advance.
Maybe I'm misunderstanding, but aren't you covering your grid background colour with a fixed gradient fill?
Ok, we've figured out how to fix that annoying thing:
We've created a converter than will receive the value, and returns the color than we wanted. Before, I was trying to do with a Property:
#region Converters
/// <summary>
/// Retorna a cor do estado da placa
/// </summary>
public class RetornaCorEstadoBarra : DependencyObject, IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
try
{
var ColorValue = (Int32)value;
if (ColorValue < 800)
return "Aqua";
else if (ColorValue < 1000)
return "Gray";
else //if (ColorValue > 1001)
return "Black";
}
catch
{
return "Black";
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
#endregion
Created a Resource inside xaml file:
<vm:RetornaCorEstadoBarra x:Key="RetornaCorEstadoBarra" />
And created a Style a little below that Resource:
<Style x:Key="ColorByGradeColumn" TargetType="DVC:ColumnDataPoint">
<Setter Property="Template" >
<Setter.Value>
<ControlTemplate TargetType="{x:Type DVC:ColumnDataPoint}">
<Border
BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"
Background="{Binding Slab.InfThick,Converter={StaticResource RetornaCorEstadoBarra}}"
>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Ok, now's the catch:
<DVC:Chart x:Name="ColumnChart" Grid.ColumnSpan="2" Width="{Binding Path=GridWidthSize}" >
<DVC:ColumnSeries
AnimationSequence="FirstToLast" FlowDirection="LeftToRight" Title="Largura" ItemsSource="{Binding Path=Placas, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ToolTip="{Binding Path=Slab.SlabId}" DependentValueBinding="{Binding Path=Slab.InfThick}" IndependentValueBinding="{Binding Path=Slab.SlabId}"
DataPointStyle="{StaticResource ColorByGradeColumn}">
<DVC:ColumnSeries.IndependentAxis>
<DVC:CategoryAxis Orientation="X" Visibility="Visible" Foreground="Transparent"/>
</DVC:ColumnSeries.IndependentAxis>
</DVC:ColumnSeries>
</DVC:Chart>
</ScrollViewer>
Well, the problem is, the color into the Chart is a Static Resource. It doesn't change a second time. So, with a 'improvised' dynamic resource, the problem is fixed, right here:
Background="{Binding Slab.InfThick,Converter={StaticResource RetornaCorEstadoBarra}}"
We pass the parameter to that Converter RetornaCorEstadoBarra. It will receive the parameter, and return a color value. Then, inside my Chart, than Binds to my Resource, will populate the Chart with the value received by my Converter. But the Chart only gets the value once. However, my converter will always return a value when he receive a value. That's the catch.
Thanks for the help :)

How can I achieve a dashed or dotted border in WPF?

I have a ListViewItem that I am applying a Style to and I would like to put a dotted grey line as the bottom Border.
How can I do this in WPF? I can only see solid color brushes.
This worked great in our application, allowing us to use a real Border and not mess around with Rectangles:
<Border BorderThickness="1,0,1,1">
<Border.BorderBrush>
<DrawingBrush Viewport="0,0,8,8" ViewportUnits="Absolute" TileMode="Tile">
<DrawingBrush.Drawing>
<DrawingGroup>
<GeometryDrawing Brush="Black">
<GeometryDrawing.Geometry>
<GeometryGroup>
<RectangleGeometry Rect="0,0,50,50" />
<RectangleGeometry Rect="50,50,50,50" />
</GeometryGroup>
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup>
</DrawingBrush.Drawing>
</DrawingBrush>
</Border.BorderBrush>
<TextBlock Text="Content Goes Here!" Margin="5"/>
</Border>
Note that the Viewport determines the size of the dashes in the lines. In this case, it generates eight-pixel dashes. Viewport="0,0,4,4" would give you four-pixel dashes.
You can create a dotted or dashes line using a rectangle like in the code below
<Rectangle Stroke="#FF000000" Height="1" StrokeThickness="1" StrokeDashArray="4 4"
SnapsToDevicePixels="True"/>
Get started with this and customize your listview according to your scenario
A bit late to the party, but the following solution worked for me. It is slightly simpler/better than both other solutions:
<Border BorderThickness="1">
<Border.BorderBrush>
<VisualBrush>
<VisualBrush.Visual>
<Rectangle StrokeDashArray="4 2" Stroke="Gray" StrokeThickness="1"
Width="{Binding RelativeSource={RelativeSource AncestorType={x:Type Border}}, Path=ActualWidth}"
Height="{Binding RelativeSource={RelativeSource AncestorType={x:Type Border}}, Path=ActualHeight}"/>
</VisualBrush.Visual>
</VisualBrush>
</Border.BorderBrush>
<TextBlock Text="Whatever" />
</Border>
Xaml
<Grid>
<Grid.RowDefinitions><RowDefinition Height="auto"/></Grid.RowDefinitions>
<Grid.ColumnDefinitions><ColumnDefinition Width="auto"/></Grid.ColumnDefinitions>
<Rectangle RadiusX="9" RadiusY="9" Fill="White" Stroke="Black" StrokeDashArray="1,2"/>
<TextBlock Padding = "4,2" Text="Whatever"/>
</Grid>
Our team got this as a requirement lately and we solved it by creating a custom control, DashedBorder which extends Border and adds the dashed border feature.
It has 3 new dependency properties
UseDashedBorder (bool)
DashedBorderBrush (Brush)
StrokeDashArray (DoubleCollection)
Usable like this
<controls:DashedBorder UseDashedBorder="True"
DashedBorderBrush="#878787"
StrokeDashArray="2 1"
Background="#EBEBEB"
BorderThickness="3"
CornerRadius="10 10 10 10">
<TextBlock Text="Dashed Border"
Margin="6 2 6 2"/>
</controls:DashedBorder>
And produces a result like this
When UseDashedBorder is set to true it will create a VisualBrush with 2 rectangles and set that as BorderBrush (that's why we need an extra property for the color of the actual BorderBrush). The first one is to create the dashing and the second of is to fill in the gaps with the Background of the border.
It maps the Rectangle dashing properties to the DashedBorder properties like this
StrokeDashArray => StrokeDashArray
Stroke => DashedBorderBrush
StrokeThickness => BorderThickness.Left
RadiusX => CornerRadius.TopLeft
RadiusY => CornerRadius.TopLeft
Width => ActualWidth
Height => ActualHeight
DashedBorder.cs
public class DashedBorder : Border
{
private static DoubleCollection? emptyDoubleCollection;
private static DoubleCollection EmptyDoubleCollection()
{
if (emptyDoubleCollection == null)
{
DoubleCollection doubleCollection = new DoubleCollection();
doubleCollection.Freeze();
emptyDoubleCollection = doubleCollection;
}
return emptyDoubleCollection;
}
public static readonly DependencyProperty UseDashedBorderProperty =
DependencyProperty.Register(nameof(UseDashedBorder),
typeof(bool),
typeof(DashedBorder),
new FrameworkPropertyMetadata(false, OnUseDashedBorderChanged));
public static readonly DependencyProperty DashedBorderBrushProperty =
DependencyProperty.Register(nameof(DashedBorderBrush),
typeof(Brush),
typeof(DashedBorder),
new FrameworkPropertyMetadata(null));
public static readonly DependencyProperty StrokeDashArrayProperty =
DependencyProperty.Register(nameof(StrokeDashArray),
typeof(DoubleCollection),
typeof(DashedBorder),
new FrameworkPropertyMetadata(EmptyDoubleCollection()));
private static void OnUseDashedBorderChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
DashedBorder dashedBorder = (DashedBorder)target;
dashedBorder.UseDashedBorderChanged();
}
private Rectangle GetBoundRectangle()
{
Rectangle rectangle = new Rectangle();
rectangle.SetBinding(Rectangle.StrokeThicknessProperty, new Binding() { Source = this, Path = new PropertyPath("BorderThickness.Left") });
rectangle.SetBinding(Rectangle.RadiusXProperty, new Binding() { Source = this, Path = new PropertyPath("CornerRadius.TopLeft") });
rectangle.SetBinding(Rectangle.RadiusYProperty, new Binding() { Source = this, Path = new PropertyPath("CornerRadius.TopLeft") });
rectangle.SetBinding(Rectangle.WidthProperty, new Binding() { Source = this, Path = new PropertyPath(ActualWidthProperty) });
rectangle.SetBinding(Rectangle.HeightProperty, new Binding() { Source = this, Path = new PropertyPath(ActualHeightProperty) });
return rectangle;
}
private Rectangle GetBackgroundRectangle()
{
Rectangle rectangle = GetBoundRectangle();
rectangle.SetBinding(Rectangle.StrokeProperty, new Binding() { Source = this, Path = new PropertyPath(BackgroundProperty) });
return rectangle;
}
private Rectangle GetDashedRectangle()
{
Rectangle rectangle = GetBoundRectangle();
rectangle.SetBinding(Rectangle.StrokeDashArrayProperty, new Binding() { Source = this, Path = new PropertyPath(StrokeDashArrayProperty) });
rectangle.SetBinding(Rectangle.StrokeProperty, new Binding() { Source = this, Path = new PropertyPath(DashedBorderBrushProperty) });
Panel.SetZIndex(rectangle, 2);
return rectangle;
}
private VisualBrush CreateDashedBorderBrush()
{
VisualBrush dashedBorderBrush = new VisualBrush();
Grid grid = new Grid();
Rectangle backgroundRectangle = GetBackgroundRectangle();
Rectangle dashedRectangle = GetDashedRectangle();
grid.Children.Add(backgroundRectangle);
grid.Children.Add(dashedRectangle);
dashedBorderBrush.Visual = grid;
return dashedBorderBrush;
}
private void UseDashedBorderChanged()
{
if (UseDashedBorder)
{
BorderBrush = CreateDashedBorderBrush();
}
else
{
ClearValue(BorderBrushProperty);
}
}
public bool UseDashedBorder
{
get { return (bool)GetValue(UseDashedBorderProperty); }
set { SetValue(UseDashedBorderProperty, value); }
}
public Brush DashedBorderBrush
{
get { return (Brush)GetValue(DashedBorderBrushProperty); }
set { SetValue(DashedBorderBrushProperty, value); }
}
public DoubleCollection StrokeDashArray
{
get { return (DoubleCollection)GetValue(StrokeDashArrayProperty); }
set { SetValue(StrokeDashArrayProperty, value); }
}
}
Working on a user control....
I have been trying a storyboard for a marching ants border. The basic grid with a rectangle and text works fine since there is no interaction. When trying to put a button inside the grid, then either the rectangle or button is visible but never both of them.
From another post:
Advanced XAML Animation effects. Pulse, Marching ants, Rotations. Alerts
Using dotNet's solution for the VisualBrush shifted the rectangle to the border with a button inside. This worked perfectly.
<UserControl.Resources>
<ResourceDictionary>
<Style TargetType="{x:Type TextBlock}" x:Key="LOC_DG_Cell_Mid" BasedOn="{StaticResource DG_TextBlock_Mid}" >
<Setter Property="Margin" Value="5 0"/>
</Style>
<Storyboard x:Key="MarchingAnts">
<DoubleAnimation BeginTime="00:00:00"
Storyboard.TargetName="AlertBox"
Storyboard.TargetProperty="StrokeThickness"
To="4"
Duration="0:0:0.25" />
<!-- If you want to run counter-clockwise, just swap the 'From' and 'To' values. -->
<DoubleAnimation BeginTime="00:00:00" RepeatBehavior="Forever" Storyboard.TargetName="AlertBox" Storyboard.TargetProperty="StrokeDashOffset"
Duration="0:3:0" From="1000" To="0"/>
</Storyboard>
</ResourceDictionary>
</UserControl.Resources>
<UserControl.Triggers>
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<BeginStoryboard Storyboard="{StaticResource MarchingAnts}"/>
</EventTrigger>
</UserControl.Triggers>
<Grid>
<Border BorderThickness="1">
<Border.BorderBrush>
<VisualBrush>
<VisualBrush.Visual>
<Rectangle x:Name="AlertBox" Stroke="Red" StrokeDashOffset="2" StrokeDashArray="5" Margin="5"
Width="{Binding RelativeSource={RelativeSource AncestorType={x:Type Border}}, Path=ActualWidth}"
Height="{Binding RelativeSource={RelativeSource AncestorType={x:Type Border}}, Path=ActualHeight}"/>
</VisualBrush.Visual>
</VisualBrush>
</Border.BorderBrush>
<Button x:Name="FinishedButton" Padding="0 5" Margin="0" Style="{StaticResource IconButton}" >
<StackPanel Orientation="Horizontal" >
<Label Style="{StaticResource ButtonLabel}" Content="Processing has Finished" />
</StackPanel>
</Button>
</Border>
</Grid>

Center a polygon within a Grid control

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

Silverlight: Binding a property of a resource object

I'm trying to bind a property of a resource object to a control (a combo...) - the thing seems to work in the designer, but it is not working at runtime.
Just using a simple page with only a button and a combo - resource section:
<UserControl.Resources>
<LinearGradientBrush x:Key="myBrush" EndPoint="1,0.5" StartPoint="0,0.5">
<GradientStop Offset="0" Color="{Binding ElementName=w_comboColor, Path=SelectedItem.Content}" />
<GradientStop Offset="1" Color="White" />
</LinearGradientBrush>
</UserControl.Resources>
and the widgets section:
<Button Name="w_button" Grid.Row="0" Width="200" Content="Button" Height="60" HorizontalAlignment="Center"
Margin="2" VerticalAlignment="Center" Background="{Binding Source={StaticResource myBrush}}">
</Button>
<ComboBox Grid.Row="1" Height="24" HorizontalAlignment="Stretch" Margin="2"
Name="w_comboColor" VerticalAlignment="Center" SelectedIndex="1" >
<ComboBox.Items>
<ComboBoxItem Content="Red" />
<ComboBoxItem Content="Blue" />
<ComboBoxItem Content="Green" />
</ComboBox.Items>
</ComboBox>
changing the value of the SelectedIndex property of the combo, in the designer, makes the button background to change its background color (as expected).
If I run the sample, nothing works any more :-\
I tried to force the DataContext of the UserControl and other stuff - nothing happens: at runtime the binding is broken.
Any ideas?
You need to add a ValueConverter to your GradientStop.Color binding to convert from a string to a Color. Your current binding is trying to assign a string to a Color property. I would imagine the designer will do the type conversion for you, just like it would be done in XAML, but that won't happen at runtime. You will need a converter something like this:
public class ColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if(value == "Red")
return Colors.Red;
else if(value == "Blue")
return Colors.Blue;
else if(value == "Green")
return Colors.Green;
}
}
This definitely is not a complete converter, but it should point you in the right direction.
Bindings on resource objects are ignored when these objects inherit directly from DependencyObject (like GradientStop does). Bindings work on resource objects that inherit from FrameworkElement.

Resources