Add n rectangles to canvas with MVVM in WPF - c#

I want to add a set of rectangles to the main window of my mvvm application. In my viewModel I've got a collection of objects which I convert to System.Windows.Shapes.Rectangle classes with a converter (code below):
ViewModel:
RecognizedValueViewModel
{
public ObservableCollection<BarcodeElement> BarcodeElements
{
get { return _BarcodeElements; }
set { _BarcodeElements = value; }
}
public RecognizedValueViewModel()
{
BarcodeElements = InitializeBarcodeElements();
}
}
Converter:
public BarcodeElementToRectangleConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Rectangle barcodeRectangle = GetRectangleFromBarcodeElement(value as BarcodeElement);
return barcodeRectangle;
}
}
The rectangles should be shown in a canvas in my MainWindow:
<Canvas x:Name="Canvas_Image_Main">
<!-- Show rectangles here -->
</Canvas>
I would add Rectangles to canvas in code but I don't now how many rectangles are there at runtime. Is there a way how I can achieve this? Tank you.

In a proper MVVM approach you would have a view model with an abstract representation of a list of rectangles, e.g. like this:
public class RectItem
{
public double X { get; set; }
public double Y { get; set; }
public double Width { get; set; }
public double Height { get; set; }
}
public class ViewModel
{
public ObservableCollection<RectItem> RectItems { get; set; }
}
Then you would have a view that uses an ItemsControl to visualize a collection of such Rect items. The ItemsControl would have a Canvas as its ItemsPanel and an appropriate ItemContainerStyle and ItemTemplate which each bind to the appropriate view model properties. It might look like this:
<ItemsControl ItemsSource="{Binding RectItems}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding X}"/>
<Setter Property="Canvas.Top" Value="{Binding Y}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Rectangle Width="{Binding Width}" Height="{Binding Height}" Fill="Black"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
An alternative without Bindings in Style Setters (which don't work in UWP) might look like this:
<ItemsControl ItemsSource="{Binding RectItems}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Rectangle Width="{Binding Width}" Height="{Binding Height}" Fill="Black">
<Rectangle.RenderTransform>
<TranslateTransform X="{Binding X}" Y="{Binding Y}"/>
</Rectangle.RenderTransform>
</Rectangle>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

You can bind the collection of rectangles to an ItemControl and set its height, width and margin:
<ItemsControl ItemsSource="{Binding Path=RectangleCollection,Mode=TwoWay}">
<ItemsControl.ItemTemplate>
<DataTemplate >
<Canvas>
<Rectangle Stroke="Black" Heigth={some converter} Width={some converter} Margin={Some Converter}>
</Canvas>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemControl>
Just an idea to get you started...

Related

ItemsControl having multiple DataTemplate in WPF

I would like to draw in canvas different shapes. How can I make object from ArrowsItems ObservableCollection and CircleItems ObservableCollecton visible in canvas? I am also creating Shapes ObservableCollection including every Circle and Arrows Items. I think that propably the reason is in the data binding but don't know where.
The goal is possibility to generate and then draw programmatically circles and arrows.
<Button Grid.Row="1" MaxWidth="1000" Command="{Binding CreateEllipse}">Utwórz</Button>
<Viewbox Grid.Row="2" Margin="0 20 0 0" Stretch="Uniform" StretchDirection="Both" VerticalAlignment="Stretch">
<ItemsControl Name="Shape" ItemsSource="{Binding Shapes}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Width="2000" Height="1200" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding X, Mode=TwoWay}"/><Setter Property="Canvas.Top" Value="{Binding Y, Mode=TwoWay}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type core:CircleItem}">
<Viewbox Width="{Binding Width}" Height="{Binding Height}">
<!--MouseMove="Viewbox_MouseMove"-->
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseMove">
<i:InvokeCommandAction Command="{Binding DataContext.MyCommand, ElementName=Shape}" CommandParameter="{Binding}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<i:Interaction.Behaviors>
<local:DragBehavior/>
</i:Interaction.Behaviors>
<Grid>
<Grid.RenderTransform>
<TranslateTransform X="{Binding TransformX}" Y="{Binding TransformY}" />
</Grid.RenderTransform>
<Ellipse Width="{Binding Width}" Height="{Binding Height}" Fill="{Binding Color}" />
<TextBlock HorizontalAlignment="Center" Text="{Binding Text}" TextAlignment="Center" VerticalAlignment="Center" />
</Grid>
</Viewbox>
</DataTemplate>
<DataTemplate DataType="{x:Type core:ArrowItem}">
<Line X1="{Binding X1}" Y1="{Binding Y1}" X2="{Binding X2}" Y2="{Binding Y2}" Stroke="{Binding Color}" StrokeThickness="{Binding StrokeThickness}" />
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
</Viewbox>
Also in my ViewModel:
public ObservableCollection<CircleItem> CircleItems { get; set; }
public ObservableCollection<ArrowItem> ArrowItems { get; set; }
public CompositeCollection Shapes { get; set; }
And after adding some objects of CircleItem class to CircleItems and ArrowItem to ArrowItems:
CompositeCollection coll = new CompositeCollection();
coll.Add(new CollectionContainer() { Collection = CircleItems });
coll.Add(new CollectionContainer() { Collection = ArrowItems });
Shapes = coll;
Make sure you initialize the Shapes property before the view model is assigned to the DataContext. The collection properties should all be readonly, otherwise you would have to fire a property change notification from their setters.
public class ViewModel
{
public ObservableCollection<CircleItem> CircleItems { get; }
= new ObservableCollection<CircleItem>();
public ObservableCollection<ArrowItem> ArrowItems { get; }
= new ObservableCollection<ArrowItem>();
public CompositeCollection Shapes { get; }
= new CompositeCollection();
public ViewModel()
{
Shapes.Add(new CollectionContainer { Collection = CircleItems });
Shapes.Add(new CollectionContainer { Collection = ArrowItems });
}
}
If I understood you correctly, you want to have different templates for different datatypes. This is quite easy to get. One of the ways (probably the simplest one):
create data templates for every of your types you want to show
create some kind of data template that will select the proper template:
<!-- Data template for arrows -->
<DataTemplate x:Key="ArrowsDataTemplate" DataType="{x:Type core:ArrowItem}">
<!-- Create here your template for arrows -->
</DataTemplate>
<!-- create data templates for other stuff and then "selector" data template -->
<DataTemplate x:Key="ContentDataTemplate">
<ContentPresenter x:Name="itemContentPresenter"
ContentTemplate="{StaticResource CircleDataTemplate}" <!-- just the default one -->
Content="{TemplateBinding Content}"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding MyItemTypeAsEnum}" Value="Arrow">
<Setter TargetName="itemContentPresenter" Property="ContentTemplate" Value="{StaticResource ArrowsDataTemplate"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
<!-- Now in your control (for example content control), where you show this stuff, do: -->
<ContentControl ContentTemplate="{StaticResource ContentDataTemplate}"/>
Now, I assumed that you have one base Item with property MyItemTypeAsEnum which will give you Circle for CircleItem, Arrow for ArrowItem etc. But if you don't have such property, you should be able to get boolean value from your viewmodel that will tell you if this item is Circle or not etc.
Into your main control, when you show thigs you have to set ContentTemplate to your "selector" template.

Displaying different data types in one canvas

based on ItemsControl only shows first Item of each object I need your help again.
The solution worked out very well but after vacation and doing other stuff I need to modify it.
Actually there are Objects (CanvasObject) which are drawn on startup/after reading xml data and others (Module) which are dropped into the canvas from a treeview. These Modules are showing up in the canvas visual children but I cant see them at the canvas.
This is my code so far - I tried different approaches but always got similar results:
C#:
public class CanvasLine
{
public Point P1 { get; set; }
public Point P2 { get; set; }
public Brush Stroke { get; set; }
public double StrokeThickness { get; set; }
public DoubleCollection StrokeDashArray { get; set; }
}
public class CanvasObject
{
public String Name { get; set; }
public ObservableCollection<CanvasLine> CanvasLines { get; set; }
}
public class Module
{
public Dictionary<String, String> Attributes { get; set; }
public Point AnchorPoint { get; set; }
public Double Height { get; set; }
public Double Width { get; set; }
}
public class ViewModel
{
...
public ObservableCollection<Object> CanvasObjects;
...
}
The Viewmodels ObservableCollection<Object> gets filled with CanvasObjects from a xml - these ones are shown correctly. After that I'm adding Modules to that Collection by dropping them to the canvas. These ones seems to be into the canvas children collection but are not visible.
Here is the corresponding / cleaned up XAML:
<ItemsControl Grid.Column="1" Grid.Row="2" Margin="0" ItemsSource="{Binding CanvasObjects, UpdateSourceTrigger=PropertyChanged}">
<ItemsControl.Resources>
<ScaleTransform x:Key="LineTransform"
ScaleX="{Binding ActualWidth,
RelativeSource={RelativeSource AncestorType=ItemsControl}}"
ScaleY="{Binding ActualWidth,
RelativeSource={RelativeSource AncestorType=ItemsControl}}"/>
</ItemsControl.Resources>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="Transparent" AllowDrop="True" Drop="Canvas_OnDrop"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplateSelector>
<xmlTemp:TemplateSelector>
<xmlTemp:TemplateSelector.ModuleTemplate>
<DataTemplate DataType="{x:Type xmlElements:Module}">
<ItemsControl ItemsSource="{Binding RelativeSource={RelativeSource Self}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding AnchorPoint.X}"/>
<Setter Property="Canvas.Top" Value="{Binding AnchorPoint.Y}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Rectangle Width="{Binding Width}" Height="{Binding Height}" StrokeThickness="2" Fill="Black" Stroke="Black">
<Rectangle.RenderTransform>
<StaticResource ResourceKey="LineTransform"/>
</Rectangle.RenderTransform>
</Rectangle>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</xmlTemp:TemplateSelector.ModuleTemplate>
<xmlTemp:TemplateSelector.LineObjectTemplate>
<DataTemplate DataType="{x:Type dt:CanvasObject}">
<ItemsControl ItemsSource="{Binding CanvasLines}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Path Stroke="{Binding Stroke}"
StrokeDashArray="{Binding StrokeDashArray}">
<Path.Data>
<LineGeometry
Transform="{StaticResource LineTransform}"
StartPoint="{Binding P1}"
EndPoint="{Binding P2}"/>
</Path.Data>
<Path.Style>
<Style TargetType="Path">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="StrokeThickness">
<Setter.Value>
<MultiBinding Converter="{StaticResource MultiplicationConverter}">
<Binding Path="StrokeThickness" Mode="OneWay"/>
<Binding Source="3"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="IsMouseOver" Value="False">
<Setter Property="StrokeThickness" Value="{Binding StrokeThickness, Mode=OneWay}"/>
</Trigger>
</Style.Triggers>
</Style>
</Path.Style>
</Path>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</xmlTemp:TemplateSelector.LineObjectTemplate>
</xmlTemp:TemplateSelector>
</ItemsControl.ItemTemplateSelector>
</ItemsControl>
Here are the missing event and template selector:
private void Canvas_OnDrop(Object sender, DragEventArgs e)
{
Module Module = e.Data.GetData("Module") as Module;
Canvas Canvas = sender as Canvas;
if (Module != null && Canvas != null)
{
Module.Width = 0.1;
Module.Height = 0.2;
Module.AnchorPoint = e.GetPosition(Canvas);
ViewModel.CanvasObjects.Add(Module);
}
}
public class TemplateSelector : DataTemplateSelector
{
public DataTemplate LineObjectTemplate { get; set; }
public DataTemplate ModuleTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is CanvasObject)
return LineObjectTemplate;
if (item is Module)
return ModuleTemplate;
return base.SelectTemplate(item, container);
}
}
If I'm changing the ModuleTemplates ItemsControl to a ListBox I can see at least that there is some selected element in the top left corner after dropping a Module somewhere in the canvas. I'm thinking that the Rectangles Container might have no size but I'm not sure how to handle that correctly. Also the dropping position has no effect.
Possibly it's just a stupid error, just like the transparent, not dropable/hitable background which costs me over an hour today and I'm "seeing" it tomorrow after a few hours of sleep but every help and hint is highly appreciated!
Greetings
Edit:
THX to elgonzo I found the error. The Modules ItemsControl was unnecessary! THX again, perfect time to finish work now ;)
Following Code works fine:
<ItemsControl Grid.Column="1" Grid.Row="2" Margin="0" ItemsSource="{Binding CanvasObjects, UpdateSourceTrigger=PropertyChanged}">
<ItemsControl.Resources>
<ScaleTransform x:Key="LineTransform"
ScaleX="{Binding ActualWidth,
RelativeSource={RelativeSource AncestorType=ItemsControl}}"
ScaleY="{Binding ActualWidth,
RelativeSource={RelativeSource AncestorType=ItemsControl}}"/>
</ItemsControl.Resources>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="Transparent" AllowDrop="True" Drop="Canvas_OnDrop"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplateSelector>
<xmlTemp:TemplateSelector>
<xmlTemp:TemplateSelector.ModuleTemplate>
<DataTemplate DataType="{x:Type xmlElements:Module}">
<DataTemplate.Resources>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding AnchorPoint.X}"/>
<Setter Property="Canvas.Top" Value="{Binding AnchorPoint.Y}"/>
</Style>
</DataTemplate.Resources>
<Rectangle Width="{Binding Width}" Height="{Binding Height}" StrokeThickness="2" Fill="Black" Stroke="Black">
<Rectangle.RenderTransform>
<StaticResource ResourceKey="LineTransform"/>
</Rectangle.RenderTransform>
</Rectangle>
</DataTemplate>
</xmlTemp:TemplateSelector.ModuleTemplate>
<xmlTemp:TemplateSelector.LineObjectTemplate>
<DataTemplate DataType="{x:Type dt:CanvasObject}">
<ItemsControl ItemsSource="{Binding CanvasLines}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Path Stroke="{Binding Stroke}"
StrokeDashArray="{Binding StrokeDashArray}">
<Path.Data>
<LineGeometry
Transform="{StaticResource LineTransform}"
StartPoint="{Binding P1}"
EndPoint="{Binding P2}"/>
</Path.Data>
<Path.Style>
<Style TargetType="Path">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="StrokeThickness">
<Setter.Value>
<MultiBinding Converter="{StaticResource MultiplicationConverter}">
<Binding Path="StrokeThickness" Mode="OneWay"/>
<Binding Source="3"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="IsMouseOver" Value="False">
<Setter Property="StrokeThickness" Value="{Binding StrokeThickness, Mode=OneWay}"/>
</Trigger>
</Style.Triggers>
</Style>
</Path.Style>
</Path>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</xmlTemp:TemplateSelector.LineObjectTemplate>
</xmlTemp:TemplateSelector>
</ItemsControl.ItemTemplateSelector>
</ItemsControl>

A master-detail TabControl binding (1:n) using parent SelectedItem with ICommand

I have XAML related question I have tried to research an answer in vain. I have commented the relevant questions to the XAML. It looks to me this questions is a more complex because of the way I try to arrange things.
Basically I have a main view model used in the TabControl headers and then in the content area I would show a list of items from the main view model. I just don't know how to to the binding. This is the main question. However, I suspect the next and ultimate objectives I have might factor in how to think about this, so I added them too. The rest of the code is for the sake of completeness.
<StackPanel>
<TabControl x:Name="mainsTabControl"
IsSynchronizedWithCurrentItem="True"
IsEnabled="True"
Visibility="Visible"
ItemsSource="{Binding Path=Mains}">
<!-- How to select a different background for the selected header? Note that the background color is "selected tab" if MainContentViewModel.IsActive is not TRUE.
If it is, a different color is chosen. Here this fact is just emulated with IsEnabled property due to well, multi-binding to the rescue (and a converter)? -->
<!-- Is there a clean way to use ICommand binding (RelayCommand) to check if it is OK to change the tab and if necessary, present a dialogue asking for the change? -->
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}">
<Setter Property="IsEnabled" Value="{Binding IsActive}"/>
</Style>
</TabControl.ItemContainerStyle>
<!-- This binds to every item in the MainViewModel.Mains collection. Note the question about background color. -->
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}" HorizontalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<!-- This binding gives reference to the selected MainContentViewModel. -->
<StackPanel Orientation="Horizontal" VerticalAlignment="Top" Margin="10" DataContext="{Binding ElementName=mainsTabControl, Path=SelectedItem, Mode=OneWay}">
<ItemsControl ItemsSource="{Binding Path=CustomItems}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</StackPanel>
using GalaSoft.MvvmLight;
using System.Collections.ObjectModel;
using System.Linq;
namespace WpfDependencyInjection.ViewModel
{
public class MainContentViewModel: ViewModelBase
{
private ObservableCollection<CustomItemViewModel> customItems;
private mainContentDto MainContent { get; set; }
public string Name { get; }
public bool isActive;
public MainContentViewModel(Engine engine, mainContentDto mainContent)
{
MainContent = mainContent;
Name = MainContent.Name;
IsActive = true;
//The custom items belonging to this main content.
var customItems = engine.CustomItemContents.Where(i => i.MainContentId == MainContent.Id).Select(i => new CustomItemViewModel(engine, i));
CustomItems = new ObservableCollection<CustomItemViewModel>(customItems);
}
public ObservableCollection<CustomItemViewModel> CustomItems
{
get
{
return customItems;
}
set
{
customItems = value;
RaisePropertyChanged(nameof(CustomItems));
}
}
public bool IsActive
{
get
{
return isActive;
}
private set
{
isActive = value;
RaisePropertyChanged(nameof(IsActive));
}
}
}
}
public class CustomItemViewModel: ViewModelBase
{
private Engine Engine { get; }
private ItemTypeDto CustomItem { get; set; }
public string Name { get; }
public CustomItemViewModel(Engine engine, ItemTypeDto customItem)
{
Engine = engine;
CustomItem = customItem;
Name = customItem.Name;
}
}
namespace WpfDependencyInjection
{
public class Engine
{
public string Name { get; } = "EngineMan";
public List<mainContentDto> MainContents { get; set; } = new List<mainContentDto>(new[]
{
new mainContentDto { Name = "Main One", Id = Guid.Parse("C51AC758-504B-4914-92DC-5EBE9A1F39E1"), Version = 1 },
new mainContentDto { Name = "Main Two", Id = Guid.Parse("C51AC758-504B-4914-92DC-5EBE9A1F39E2"), Version = 1 }
});
public List<ItemTypeDto> CustomItemContents { get; set; } = new List<ItemTypeDto>(new ItemTypeDto[]
{
new ItemType1Dto { MainContentId = Guid.Parse("C51AC758-504B-4914-92DC-5EBE9A1F39E1"), Name = "ItemType1Dto I", Id = Guid.NewGuid(), Version = 1 },
new ItemType2Dto { MainContentId = Guid.Parse("C51AC758-504B-4914-92DC-5EBE9A1F39E1"), Name = "ItemType2Dto I", Id = Guid.NewGuid(), Version = 1 },
new ItemType2Dto { MainContentId = Guid.Parse("C51AC758-504B-4914-92DC-5EBE9A1F39E2"), Name = "ItemType2Dto 2", Id = Guid.NewGuid(), Version = 1 }
});
public Engine()
{
}
}
}
<edit: The binding solved partially, though not the ICommand part.
Try this:
<TabControl x:Name="mainsTabControl"
IsEnabled="True"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Path=Mains}"
SelectedItem="0"
Visibility="Visible">
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Border Background="{TemplateBinding Background}">
<ContentPresenter ContentSource="Header" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="HotPink" />
</Trigger>
</Style.Triggers>
</Style>
</TabControl.ItemContainerStyle>
<TabControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:MainContentViewModel}">
<Button Background="{x:Null}"
Command="{Binding SomeCommand}"
Content="{Binding Name}"
FocusVisualStyle="{x:Null}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type local:MainContentViewModel}">
<ItemsControl Margin="10"
VerticalAlignment="Top"
ItemsSource="{Binding Path=CustomItems}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:CustomItem}">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
Command:
There may be cleaner ways but here we bind IsSelected for the TabItem to the IsSelected property of the viewmodel. This enables having a command that asks if it is ok to navigate and sets IsSelected to true if it is.
Background:
We also retemplate the tabitem so that background works as we want. If you check with Snoop WPF inserts an extra border when the item is selected.
Side note 1:
Don't put the TabControl into a StackPanel like that. A StackPanel sizes to content and will kill scrolling and draw outside the control. Also it comes with a cost, a deep visual tree is not cheap. Same in the ItemTemplate and the other places. In fact StackPanel is rarely right for anything :)
Side note 2:
If you specify DataType in your DataTemplate you get intellisense and some compiletime checking.

add icons to viewBox

I have a Viewbox as ChessBoard
<Viewbox>
<ItemsControl Name="ChessBoard">
<ItemsControl.ItemsPanel >
<ItemsPanelTemplate >
<Canvas Width="8" Height="8" Background="{StaticResource Checkerboard}" MouseDown="Canvas_MouseDown" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Viewbox>
How i can add icon to the rectangles?
First of all... Your chess board is the ItemsControl, not the Viewbox. The Viewbox is simply containing the board for some special visualization woe we don't know (Maybe so you can use a Height of 8 without it appearing extremely tiny?).
Since you're using an ItemsControl, the correct way would be adding items to it and setting several DataTemplates, one for each type of piece. The "icon" would go inside each DataTemplate.
For instance, if your chess pieces are defined like this:
public class ChessPiece
{
public ColorEnum Color { get; set; }
public int X { get; set; }
public int Y { get; set; }
public virtual bool IsAValidMovement(int x, int y)
{
// base common logic
}
}
public class QueenPiece : ChessPiece
{
public override bool IsAValidMovement(int x, int y)
{
if (base.IsAValidMovement(x, y))
{
// specific logic
}
}
}
Then in your XAML you should do something like this:
<Viewbox>
<ItemsControl Name="ChessBoard">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Width="8" Height="8" Background="{StaticResource Checkerboard}" MouseDown="Canvas_MouseDown" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<!-- You could also bind ItemsSource to a collection of chess pieces -->
<ItemsControl.Items>
<local:QueenPiece Color="Black" X="3" Y="0" />
</ItemsControl.Items>
</ItemsControl>
</Viewbox>
And in your Resources include a DataTemplate of the correct TargetType:
<Window.Resources>
<DataTemplate TargetType="{x:Type local:QueenPiece}">
<Grid Height="1" Width="1" />
<Image x:Name="Icon" Source="queen-white.png" Stretch="Uniform" />
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Color}" Value="Black">
<Setter TargetName="Icon" Property="Source" Value="queen-black.png" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Window.Resources>
I'll leave positioning the pieces to you :P
A ItemControl is normally used to contain a collection of items.The ItemsPanelTemplate in the ItemControl usually describes how you want that collection of items to be displayed. These are normally generic types provided for by Microsoft. These include the WrapPanel and StackPanel. The item itself would be underneath the ItemTemplate. There you have the item template be customized to the viewmodel that is in the itemsource.
This should be how you look at it to start out.
<ItemsControl Name="ChessBoard" Width="50">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderThickness="1" BorderBrush="Black">
<Canvas Height="10"
Width="10"
Background="{Binding Color}">
</Canvas>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The setting of the items control is done in the code behind.
public MainWindow()
{
InitializeComponent();
List<stage> l = new List<stage>();
l.Add(new stage() { Color = "White" });
l.Add(new stage() { Color = "Black" });
l.Add(new stage() { Color = "White" });
l.Add(new stage() { Color = "Black" });
l.Add(new stage() { Color = "White" });
l.Add(new stage() { Color = "Black" });
l.Add(new stage() { Color = "White" });
l.Add(new stage() { Color = "Black" });
l.Add(new stage() { Color = "White" });
l.Add(new stage() { Color = "Black" });
ChessBoard.ItemsSource = l;
}
I need to let you know, this is not really a robust way of doing these thing. But this is the right direction you want to head in. But to just get an image in the canvas you will just need to add in an imagecontrol and bind to source.
<Canvas Height="10"
Width="10"
Background="{Binding Color}">
<Image Source="{Binding ImageSource}"/>
</Canvas>

ToolTip use to show the index of an item from a collection. C# WPF MVVM

I'm plotting a histogram of a grayscale image using an array of 256 values. Im doing so by creating my own chart with 256 vertical rectangles (columns). My aim is that when I mouse over one rectangle I get its index value, its index from the array it lies in, like if I mouse over the 200th rectangle I get 200 in a small text box near cursor, like how ToolTip should perform. The problem is that I don't find the proper binding for ToolTip to get this working. I think the solution lies in the proper use of AlternationCount / AlternationIndex
Here is may XAML code, maybe someone can give me a fix for it:
<ItemsControl Grid.Row="1" ItemsSource="{Binding HistogramValues}" AlternationCount="{Binding Path=HistogramValues.Count}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Rectangle Height="{Binding }" ToolTip="{Binding AlternationIndex, RelativeSource={RelativeSource AncestorType=ItemsControl}}" Width="2" VerticalAlignment="Bottom" Fill="Black"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
where in view model I have:
public float[] HistogramValues { get; set; }
I've found this useful post
Numbered listbox
but I still cant make it run for my case, I'm confused about that ItemTemplate and TemplatedParent which I dont know if I need or if I need the template how should I code this.
If you are not ok with converters you can use this,
<ItemsControl Grid.Row="1" ItemsSource="{Binding Collection}" AlternationCount="{Binding Path=Collection.Count}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" ToolTip="{Binding Path=(ItemsControl.AlternationIndex), RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContentPresenter}}">
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Since AlternationIndex is attached property, it should be covered with "()".
If you dont specify source in a binding, it always binds to datacontext.
Do this
ToolTip="{Binding (ItemsControl.AlternationIndex), RelativeSource={RelativeSource AncestorType=ContentPresenter}}"
If showing the index is your only requirement you can achieve this like below,,
MyConverter is a multiconverter ,and include that in resources
<ItemsControl Grid.Row="1" ItemsSource="{Binding Collection}" >
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Control.ToolTip">
<Setter.Value>
<MultiBinding Converter="{StaticResource MyConverter}">
<Binding Path="IsMouseOver" RelativeSource="{RelativeSource Self}"/>
<Binding />
<Binding Path="ItemsSource" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=ItemsControl}"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
public partial class MainWindow : Window
{
private ObservableCollection<string> collection;
public ObservableCollection<string> Collection
{
get { return collection; }
set { collection = value; }
}
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
collection = new ObservableCollection<string>();
collection.Add("First");
collection.Add("Second");
collection.Add("Third");
collection.Add("Fourth");
}
}
public class MyConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if ((bool)values[0])
{
return (values[2] as ObservableCollection<string>).IndexOf(values[1].ToString());
}
else
return "";
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}

Categories

Resources