I'm building an application with C# WPF. And I'm using MVVM architecture. I want to display some letters on an image in the given coordinates. So far, the only thing I was able to do is to render some shapes like rectangles using 'Geometry' class.
I have attached an image for references.
The grid is an image(a PNG file)
And below is my current code for the view model and XAML file.
ViewModel
public class InspectionGridViewModel : RegionViewModelBase
{
public ObservableCollection<Drawing> Drawings { get; set; }
public InspectionGridViewModel(IRegionManager regionManager, ILogger<InspectionGridViewModel> logger, MainModuleConfiguration configuration) : base(regionManager, logger)
{
PlotMarkersInTheGrid();
}
public void PlotMarkersInTheGrid()
{
Drawings = new ObservableCollection<Drawing>();
Drawings.Add(new Drawing
{
Geometry = new StreamGeometry(),
Fill = Brushes.LightBlue,
Stroke = Brushes.Blue,
StrokeThickness = 2
});
Drawings.Add(new Drawing
{
Geometry = new RectangleGeometry(new Rect(50, 150, 100, 60)),
Fill = Brushes.LightGreen,
Stroke = Brushes.Green,
StrokeThickness = 2
});
}
}
public class Drawing
{
public Geometry Geometry { get; set; }
public Brush Fill { get; set; }
public Brush Stroke { get; set; }
public double StrokeThickness { get; set; }
}
XAML file
<ItemsControl ItemsSource="{Binding Drawings}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Path Data="{Binding Geometry}"
Fill="{Binding Fill}"
Stroke="{Binding Stroke}"
StrokeThickness="{Binding StrokeThickness}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
First thing I wanna know. Is this possible to do? If so, can anyone help me with this?
How about rather than messing with geometry, you use TextBlock and use a ViewBox to scale it? You could just place these in a Canvas similiar to what you're attempting to do with those geometry items. So, your XAML would be very similiar to what you already have, but template the items into a ViewBox.
<ItemsControl ItemsSource="{Binding Path=Markers}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Viewbox Width="{Binding Size}" Height="{Binding Size}">
<TextBlock Text="{Binding MarkerText}" />
</Viewbox>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Path=PosX}" />
<Setter Property="Canvas.Top" Value="{Binding Path=PosY}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
Note that a ItemContainerStyle is used for positioning since the direct child of the canvas is required to use the Canvas.Left and Canvas.Top properties.
With that in place and view models like these ..
public class InspectionGridViewModel
{
public ObservableCollection<MarkerViewModel> Markers { get; } = new ObservableCollection<MarkerViewModel>();
public InspectionGridViewModel()
{
Markers = new ObservableCollection<MarkerViewModel>()
{
new MarkerViewModel()
{
MarkerText = "A",
Size = 50,
PosX = 10,
PosY = 20
},
new MarkerViewModel()
{
MarkerText = "B",
Size = 100,
PosX = 80,
PosY = 50
}
};
}
}
public class MarkerViewModel
{
public string MarkerText { get; set; }
public int PosX { get; set; }
public int PosY { get; set; }
public int Size { get; set; }
}
I get the following result. I think this is what you're looking for.
Related
I have dxg:GridControl.
xaml:
<dxg:GridControl Name="DynamicGridControl"
ItemsSource="{Binding CommonEditCollection, Mode=TwoWay}"
SelectionMode="Cell"
AutoGenerateColumns="AddNew"
AutoGeneratedColumns="GridControl_AutoGeneratedColumns">
<dxmvvm:Interaction.Behaviors>
<lc:CellSelectionBehavior SelectedCells="{Binding SelectedCells, Mode=TwoWay}"/>
</dxmvvm:Interaction.Behaviors>
</dxg:GridControl>
ItemsSource binds to CommonEditCollection
viewmodel:
public ObservableCollection<Dictionary<int, DynamicTableModel>> CommonEditCollection { get; set; }
model:
public class DynamicTableModel
{
public double CellWidth { get; set; }
public string StrValue{ get; set; }
public bool IsBorerNull { get; set; }
public DynamicTableModel(string strVal, double cellWidth, bool isBorerNull = false)
{
StrValue = strVal;
CellWidth = cellWidth;
IsBorerNull = isBorerNull;
}
}
In xaml file I set Resources for cells style (I want to merge some cells):
<DataTemplate x:Key="CellDataTemplate">
<StackPanel>
<Border ...
</Border.Style>
</Border>
<dxg:CellEditor Content="{Binding Value.StrValue}"/>
</StackPanel>
</DataTemplate>
I bind CellEditor to property of DynamicTableModel class. But if I try edit text in any cell it throw NullReferenceException.
I cant change class DynamicTableModel to string because I need other properties. And I tried to use convertor Attribute, but it create new instance when I change text.
Help me please to change text in cells.
Project link: https://github.com/Kolgotin/DynamicGridControl
In result I just added this property:
<DataTemplate>
<dxe:TextEdit Name="PART_Editor" HorizontalContentAlignment="Stretch">
<dxe:TextEdit.EditTemplate>
<ControlTemplate>
<dxe:TextEdit x:Name="teNewValue"
HorizontalAlignment="Stretch"
EditValue="{Binding Value.StrValue}"/>
</ControlTemplate>
</dxe:TextEdit.EditTemplate>
</dxe:TextEdit>
</DataTemplate>
and handler works in "set" method
So I'm using a WinForms application as a guideline for creating a WPF Application that displays nautical charts. The WinForms application is using a System.Windows.Forms.Panel and sets the BackgroundImage to a dynamically-created Bitmap. I have the need to add a child Canvas (or alternative Control) to the Panel so that I can display waypoints overlaying the chart.
In WPF, I have tried using a Canvas as a substitute for the Panel, but I was not able to get that to work since it has a Background property accepting a Brush and not a Bitmap.
I have also tried using a WinFormsHost to utilize the System.Windows.Forms.Panel object, but I can't use this to house another control within it.
So what I need is preferably a WPF element I can use without a WinFormsHost that I can use to set the Bitmap BackgroundImage and to add another control with graphics overlayed.
You can use ImageBrush to set the background https://learn.microsoft.com/en-us/dotnet/api/system.windows.media.imagebrush?view=netframework-4.7.2
There is however a great drawback of doeing so as your canvas will not size itself to the size of the image. You could basically use an image behind a Canvas:
<Grid>
<Image Source="..."/>
<Canvas ...>
</Canvas>
</Grid>
Whenever you need to display a list of something in WPF you generally use an ItemsControl, and this case is no different.
With ItemsControl you can override the panel it uses, and you were correct in choosing a Canvas over a Panel. Setting an image as the background is easy, you just use an ImageBrush instead.
As for your waypoints, I'm guessing you'll need to display other object types as well, so create a ViewModel for each and use a DataTemplate to select the appropriate graphic depending on the type. Each graphic that gets created will be wrapped up in a ContentPresenter, but ItemsControl also allows you to override the style of that via ItemContainerStyle, so that's where you set Canvas.Left and Canvas.Top to position your items.
Put it all together and your XAML needs to look something like this:
<Viewbox>
<ItemsControl ItemsSource="{Binding ChartElements}" Width="1000" Height="1000">
<ItemsControl.Resources>
<!-- DataTemplates here select the appropriate graphic to display for each class type -->
<DataTemplate DataType="{x:Type local:Waypoint}">
<Ellipse Width="50" Height="50" Fill="Yellow" Stroke="CornflowerBlue" StrokeThickness="5">
<Ellipse.RenderTransform>
<TranslateTransform X="-25" Y="-25" /> <!-- center the ellipse -->
</Ellipse.RenderTransform>
</Ellipse>
</DataTemplate>
<DataTemplate DataType="{x:Type local:NavigationLine}">
<Line X1="0" Y1="0" X2="{Binding Width}" Y2="{Binding Height}" Stroke="CornflowerBlue" StrokeThickness="10" StrokeDashArray="3 1" />
</DataTemplate>
</ItemsControl.Resources>
<ItemsControl.ItemsPanel>
<!-- Replace the default panel with a Canvas -->
<ItemsPanelTemplate>
<Canvas>
<Canvas.Background>
<ImageBrush ImageSource="https://images-na.ssl-images-amazon.com/images/I/A1%2Bp%2BB8wq2L._SL1500_.jpg" Stretch="Uniform" />
</Canvas.Background>
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<!-- Position each item on the canvas and set the ZIndex so that waypoints appear on top -->
<ItemsControl.ItemContainerStyle>
<Style TargetType="{x:Type ContentPresenter}">
<Setter Property="Canvas.Left" Value="{Binding X}" />
<Setter Property="Canvas.Top" Value="{Binding Y}" />
<Setter Property="Panel.ZIndex" Value="{Binding Layer}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</Viewbox>
You should always be creating a MainViewModel and doing proper MVVM, but this sample code will get you started:
public partial class MainWindow : Window
{
public List<object> ChartElements { get; } = new List<object>
{
new Waypoint{X=100, Y=100 },
new Waypoint{X=500, Y=300 },
new Waypoint{X=300, Y=500 },
new Waypoint{X=800, Y=700 },
new NavigationLine{X1=100, Y1=100, X2=500, Y2=300},
new NavigationLine{X1=500, Y1=300, X2=300, Y2=500},
new NavigationLine{X1=300, Y1=500, X2=800, Y2=700}
};
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
}
}
public class Waypoint
{
public int Layer { get; } = 1; // waypoint circles should always appear on top
public double X { get; set; }
public double Y { get; set; }
}
public class NavigationLine
{
public int Layer { get; } = 0;
public double X1 { get; set; }
public double Y1 { get; set; }
public double X2 { get; set; }
public double Y2 { get; set; }
public double X => this.X1;
public double Y => this.Y1;
public double Width => this.X2 - this.X1;
public double Height => this.Y2 - this.Y1;
}
The hot-linked image will probably take a few seconds to load, but you should wind up with this:
You could probably save a lot of work by using one of the map control libraries available for WPF. I can recommend my XAML Map Control. It provides several ways to display map bitmaps, and also has a MapItemsControl to easily display collections of items with geographic locations.
The probably easiest way to show a nautical chart is to use a WmsImageLayer:
xmlns:map="clr-namespace:MapControl;assembly=MapControl.WPF"
...
<map:Map Center="50,0" ZoomLevel="2">
<map:Map.MapLayer>
<map:WmsImageLayer ServiceUri="http://chartserver4.sevencs.com:8080"
Layers="ENC"/>
</map:Map.MapLayer>
</map:Map>
If you do not have a WMS for chart generation (like the SevenCs ChartServer), but need to convert a WinForms bitmap, you may derive from the MapImageLayer class:
public class ChartImageLayer : MapImageLayer
{
protected override Task<ImageSource> GetImageAsync(BoundingBox boundingBox)
{
// use ParentMap.MapProjection to get the current map projection
return Task.Run(() =>
{
// get SevenCs chart bitmap for the requested bounding box
System.Drawing.Bitmap chartBitmap = ...
// convert from System.Drawing.Bitmap to System.Windows.Media.ImageSource
using (var stream = new MemoryStream())
{
chartBitmap.Save(stream, System.Drawing.Imaging.ImageFormat.Png);
stream.Position = 0;
var bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.StreamSource = stream;
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.EndInit();
bitmapImage.Freeze();
return (ImageSource)bitmapImage;
}
});
}
}
and use the class like this:
<map:Map Center="50,0" ZoomLevel="2">
<map:Map.MapLayer>
<local:ChartImageLayer />
</map:Map.MapLayer>
</map:Map>
For displaying collections of of items, you would create a view model, e.g. like this:
using MapControl;
...
public class Waypoint
{
public string Label { get; set; }
public Location Location { get; set; }
}
public class ViewModel
{
public ObservableCollection<Waypoint> Waypoints { get; }
= new ObservableCollection<Waypoint>();
}
with some initialization like:
public MainWindow()
{
InitializeComponent();
var vm = new ViewModel();
vm.Waypoints.Add(new Waypoint { Label = "Iceland", Location = new Location(65, -18) });
vm.Waypoints.Add(new Waypoint { Label = "Norway", Location = new Location(71, 25) });
vm.Waypoints.Add(new Waypoint { Label = "Cyprus", Location = new Location(35, 33) });
vm.Waypoints.Add(new Waypoint { Label = "Tenerife", Location = new Location(28.25, -16.5) });
DataContext = vm;
}
In XAML, you would add a MapItemsControl like this:
<map:Map Center="50,0" ZoomLevel="2">
...
<map:MapItemsControl ItemsSource="{Binding Waypoints}">
<map:MapItemsControl.ItemContainerStyle>
<Style TargetType="map:MapItem">
<Setter Property="map:MapPanel.Location" Value="{Binding Location}"/>
</Style>
</map:MapItemsControl.ItemContainerStyle>
<map:MapItemsControl.ItemTemplate>
<DataTemplate>
<Canvas>
<Path Fill="Red">
<Path.Data>
<EllipseGeometry RadiusX="5" RadiusY="5"/>
</Path.Data>
</Path>
<TextBlock Margin="5,-5" Text="{Binding Label}"/>
</Canvas>
</DataTemplate>
</map:MapItemsControl.ItemTemplate>
</map:MapItemsControl>
</map:Map>
The result:
Zoomed in:
The following code draws rectangles in 2D grid. Everything is working fine except it is really slow when it has to draw more than 50,000 squares. I know it sounds like a lot of squares but I have the same program written in C++/Qt and it's a lot faster, it draws the 50,000 almost instantaneously wheres in C#/WPF it takes 50 seconds.
Is there a better/faster way to draw rectangles on the screen in WPF?
XAML
<Window x:Class="DrawingRectanglesWithMvvmLight.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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ignore="http://www.galasoft.ch/ignore"
mc:Ignorable="d ignore"
Height="319"
Width="453.333"
Title="MVVM Light Application"
DataContext="{Binding Main, Source={StaticResource Locator}}">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Skins/MainSkin.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid x:Name="LayoutRoot" Margin="0,0,2,0" Height="194" VerticalAlignment="Top" Background="#FF3E7AAC">
<ItemsControl ItemsSource="{Binding PartsGrid}" Height="200" Margin="5,0,10,-6">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="#FFF1F0F0" Margin="10" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Rectangle
Width="{Binding Width}"
Height="{Binding Height}"
Margin="{Binding Margin}"
Fill="{Binding Fill}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Grid Margin="5,236,6,-69">
<Button Content="Draw" Command="{Binding DrawCommand}" Margin="328,0,10,0" />
<Button Content="Reset" Command="{Binding ResetCommand}" Margin="263,0,109,0" />
<TextBox Text="{Binding Width}" RenderTransformOrigin="1.049,2.023" Margin="124,0,200,0"/>
<TextBox Text="{Binding Height}" Margin="0,0,334,0"/>
</Grid>
</Grid>
</Window>
Class:
namespace MvvmLightTEST.Model
{
public class FSRectangle
{
public double Width { get; set; }
public double Height { get; set; }
public Thickness Margin { get; set; }
public Brush Fill { get; set; }
}
}
ViewModel:
namespace DrawingRectanglesWithMvvmLight.ViewModel
{
public class MainViewModel : ViewModelBase
{
public ObservableCollection<FSRectangle> PartsGrid { get; } = new ObservableCollection<FSRectangle>();
public RelayCommand DrawCommand { get; }
public RelayCommand ResetCommand { get; }
public double Width { get; set; }
public double Height { get; set; }
public MainViewModel(IDataService dataService)
{
DrawCommand = new RelayCommand(Draw);
ResetCommand = new RelayCommand(Clear);
}
private void Draw()
{
Clear();
int xParts = 250;
int yParts = 200;
for (int i = 0; i < xParts; i++) {
for (int j = 0; j < yParts; j++) {
FSRectangle part = new FSRectangle();
part.Width = Width;
part.Height = Height;
part.Margin = new Thickness((part.Width + 1) * i, (part.Height + 1) * j, 0, 0);
part.Fill = new SolidColorBrush(Color.FromArgb(170, 51, 51, 255));
PartsGrid.Add(part);
}
}
}
private void Clear()
{
PartsGrid.Clear();
}
}
}
UI
By not using MVVM due to the slow performance for this use case. Rectangles are FrameworkElements which contain layout, a feature that does not scale This is discussed many times in SO.
You might want to consider
using DrawingVisual and ContainerVisual for lower level rendering with no layout overhead
no MVVM
create render visuals ahead of time rather than per render
Image courtesy Microsoft, used without permission.
MSDN has this to say on DrawingVisual
The DrawingVisual is a lightweight drawing class that is used to render shapes, images, or text. This class is considered lightweight because it does not provide layout or event handling, which improves its runtime performance. For this reason, drawings are ideal for backgrounds and clip art. The DrawingVisual can be used to create a custom visual object.
See also
WPF Graphics Rendering Overview
Path with RectangleGeometry is an option:
https://learn.microsoft.com/en-us/dotnet/framework/wpf/graphics-multimedia/how-to-define-a-rectangle-using-a-rectanglegeometry
I am a newbie in templating Wpf controls. I use VS2013, WPF 4.5 and Caliburn Micro 2.0.2. In part of tasks I have I need to populate a grid with toggle buttons contained different images and its subtitle. I have solved it using UniformGrid. See my code below. They work but still don't have event and property binding since I don't know how I can bind the events and properties of toggle buttons to view model, since they are generated automatically and dynamically and the number of toggle buttons is uncertain (depends on the number of images in the image folder).
For example:
manually I could bind the Click event, IsChecked property and some other properties of toggle button 1 like following:
<ToggleButton x:Name="ToggleVehicle01" IsChecked={Binding SelectedVehicle01} Background="{Binding BackColorSelectedVehicle01}" ToolTip="{Binding VehicleName01}">
But now I can't do that anymore since the toggle buttons are generated automatically and their number is uncertain. Please help. Feel free to change my code below or give me examples code that works. Thank you in advance.
The View (MainView.xaml):
<UserControl x:Class="CMWpf02.Views.MainView"
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="300"
d:DesignWidth="300"
mc:Ignorable="d">
<Grid Width="1024"
Height="768"
HorizontalAlignment="Left"
VerticalAlignment="Top"
ShowGridLines="True">
<ItemsControl Name="ImageList"
Background="#FFFFFFFF"
BorderBrush="#FFA90606"
ItemsSource="{Binding Path=VehicleImages}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Margin="0,0,0,0" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ToggleButton Width="180"
Margin="10,10,10,10"
FontSize="10"
Style="{StaticResource {x:Static ToolBar.ToggleButtonStyleKey}}">
<!-- x:Name="ToggleVehicle01" -->
<!-- Background="{Binding BackColorSelectedVehicle01}" -->
<!-- IsChecked="{Binding SelectedVehicle01}" -->
<!-- ToolTip="{Binding Vehicle01Name}"> -->
<StackPanel Margin="0,5,0,5"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Image Width="140"
RenderOptions.BitmapScalingMode="Fant"
Source="{Binding Path=Image}" />
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
FontWeight="Bold"
Text="{Binding Path=Name}" />
</StackPanel>
</ToggleButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</UserControl>
The ViewModel (MainViewModel.cs):
using Caliburn.Micro;
using System;
using System.Collections.ObjectModel;
using System.IO;
namespace CMWpf02.ViewModels
{
public class MainViewModel : Screen, IHaveDisplayName
{
private String _path2Images = #"D:\tmp\Images";
public string DisplayName { get; set; }
public ObservableCollection<VehicleImage> VehicleImages { get; set; }
public MainViewModel()
{
DisplayName = "Main Window";
var vehicles = new ObservableCollection<String>();
vehicles = GetAllFilesFromFolder(_path2Images);
VehicleImages = new ObservableCollection<VehicleImage>();
foreach (var i in vehicles)
VehicleImages.Add(new VehicleImage(i));
}
public ObservableCollection<String> GetAllFilesFromFolder(String fullPathFolder)
{
string[] fileArray = Directory.GetFiles(fullPathFolder);
return new ObservableCollection<String>(fileArray);
}
}
public class VehicleImage
{
public String Image { get; private set; }
public String Name { get; private set; }
public VehicleImage(String image)
{
Image = image;
Name = Path.GetFileName(image);
}
}
//public void ToggleVehicle01()
//{
// var selectText = (SelectedVehicle01) ? " selected" : " unselected";
// MessageBox.Show(Vehicle01Name + selectText);
// BackColorSelectedVehicle01 = (SelectedVehicle01) ? _backColorSelectedVehicle : _defaultBackColorVehicle;
//}
//public Boolean SelectedVehicle02
//{
// get { return _selectedVehicle02; }
// set
// {
// _selectedVehicle02 = value;
// NotifyOfPropertyChange(() => SelectedVehicle02);
// }
//}
//public Brush BackColorSelectedVehicle02
//{
// get { return _backColorSelectedVehicle02; }
// set
// {
// _backColorSelectedVehicle02 = value;
// NotifyOfPropertyChange(() => BackColorSelectedVehicle02);
// }
//public String Vehicle01Name { get; private set; }
}
EDIT: Now I can bind the properties of generated ToggleButton with view model. I make the VehicleImage class to a view model (see modified code below). But I still have problem to bind Click-event of generated ToggleButton to view model.
The modified class to view model
public class VehicleImage : PropertyChangedBase
{
public String Image { get; private set; }
public String Name { get; private set; }
private Boolean _selectedVehicle;
public Boolean SelectedVehicle
{
get { return _selectedVehicle; }
set
{
_selectedVehicle = value;
BackColorSelectedVehicle = _selectedVehicle ? new SolidColorBrush(Color.FromArgb(255, 242, 103, 33)) : new SolidColorBrush(Colors.White);
}
}
private Brush _backColorSelectedVehicle;
public Brush BackColorSelectedVehicle
{
get { return _backColorSelectedVehicle; }
set
{
_backColorSelectedVehicle = value;
NotifyOfPropertyChange(() => BackColorSelectedVehicle);
}
}
// ToggleButton's Click-Event Handler, but it doesn't get event trigger from View.
// Therefore I set the BackColorSelectedVehicle fin setter of SelectedVehicle property.
public void ToggleSelection()
{
//BackColorSelectedVehicle = SelectedVehicle ? new SolidColorBrush(Color.FromArgb(255, 242, 103, 33)) : new SolidColorBrush(Colors.White);
}
public VehicleImage(String image)
{
Image = image;
Name = Path.GetFileName(image);
}
}
The modified view
<ToggleButton Width="180"
Margin="10,10,10,10"
Background="{Binding Path=BackColorSelectedVehicle}"
FontSize="10"
IsChecked="{Binding Path=SelectedVehicle}"
Style="{StaticResource {x:Static ToolBar.ToggleButtonStyleKey}}"
ToolTip="{Binding Path=Name}">
<!-- x:Name="ToggleSelection" -->
<StackPanel Margin="0,5,0,5"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Image Width="140"
RenderOptions.BitmapScalingMode="Fant"
Source="{Binding Path=Image}" />
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding Path=Name}" />
</StackPanel>
</ToggleButton>
When I try to bind a ResourceDictionary item against Rectangle.Child, I get an exception:
ArgumentException: Value does not fall within the expected range.
Here is an example:
<UserControl.Resources>
<local:PersonConverter x:Key="MyConverter"/>
</UserControl.Resources>
<ListBox ItemsSource="{Binding Persons}">
<ListBox.ItemTemplate>
<DataTemplate>
<Border Child="{Binding Gender, Converter={StaticResource MyConverter}}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And the code behind:
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
Persons = new List<Person> {new Person("Female"), new Person("Male")};
}
public List<Person> Persons { get; private set; }
}
public class PersonConverter : IValueConverter
{
private ResourceDictionary Items { get; set; }
public PersonConverterRes()
{
Items = new ResourceDictionary
{
{"Male", new Canvas() {
Background = new SolidColorBrush(Colors.Blue),
Height = 100, Width = 100}},
{"Female", new Canvas() {
Background = new SolidColorBrush(Colors.Magenta),
Height = 100, Width = 100}}
};
}
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
return Items[value.ToString()];
}
...
}
public class Person
{
public Person(String gender)
{
Gender = gender;
}
public String Gender { get; private set; }
}
But if I replace the ResourceDictionary with a plain Dictionary<String, UIElement> the binding works fine:
public class PersonConverter : IValueConverter
{
private Dictionary<String, UIElement> Items { get; set; }
public PersonConverterRes()
{
Items = new Dictionary<String, UIElement>
{
{"Male", new Canvas() {
Background = new SolidColorBrush(Colors.Blue),
Height = 100, Width = 100}},
{"Female", new Canvas() {
Background = new SolidColorBrush(Colors.Magenta),
Height = 100, Width = 100}}
};
}
...
}
Does anybody know what is causing this exception?
Note:
I have tried this under WinRT as well. There, the code doesn't throw an exception, but the binding still doesn't work if I use a ResourceDictionary. I guess it's probably failing silently.
You can not use databinding to bind to the Child property of a Border since it is not a DependencyProperty. This is why your ResourceDictionary approach does not work.
Also, databinding in WPF/Silvelight/WinRT fails silently by design (it's a feature, and a very useful one if used correctly), so your guess would be right on that.
Don't do this.
More elegant to set a the Canvas.Background with trigger
<ListBox ItemsSource="{Binding Persons}">
<ListBox.ItemTemplate>
<DataTemplate>
<Border <!-- set properties --> >
<Canvas Height="100" Width="100">
<Canvas.Style>
<Style TargetType="Canvas">
<Style.Triggers>
<DataTrigger Binding={Binding Gender} Value="Male">
<Setter Property="Background" Value="Blue"/>
</DataTrigger>
<DataTrigger Binding={Binding Gender} Value="Female">
<Setter Property="Background" Value="Magenta"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Canvas.Style>
</Canvas>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>