Binding Silverlight controls collection to Grid - possible or not? - silverlight

I'm a bit new to Silverlight and now I'm developing a map app. I have a collection of custom controls (map markers, POIs, etc). Every control has a property "Location" of the type Point, where Location.X means Canvas.Left of the control, Location.Ymeans Canvas.Top of the control.
I'm trying to refactor my interface to MVVM pattern. I want to do something like this:
Say my controls are in Canvas. I want to have something like:
<Canvas DataContext="{StaticResource myModel}" ItemsSource="{Binding controlsCollection}">
<Canvas.ItemTemplate> ... </Canvas.ItemTemplate>
</Canvas>
In my custom control I want to have something like:
<myCustomControl DataContext="{StaticResource myControlModel}" Canvas.Left="{Binding Location.X}" Canvas.Top="{Binding Location.Y}" />
Is it possible? Maybe there's a better way?

I would think it is possible to use the ItemsControl control for this.
Let's say you create a holder-control that holds the position of the control + more info that you choose.
I call it "ControlDefinition.cs":
public class ControlDefinition : DependencyObject, INotifyPropertyChanged
{
public static readonly DependencyProperty TopProperty = DependencyProperty.Register("Top", typeof(Double), typeof(ControlDefinition), new PropertyMetadata(0d));
public static readonly DependencyProperty LeftProperty = DependencyProperty.Register("Left", typeof(Double), typeof(ControlDefinition), new PropertyMetadata(0d));
public static readonly DependencyProperty ModelProperty = DependencyProperty.Register("Model", typeof(Object), typeof(ControlDefinition), new PropertyMetadata(null));
public Double Top
{
get { return (Double)GetValue(TopProperty); }
set
{
SetValue(TopProperty, value);
NotifyPropertyChanged("Top");
}
}
public Double Left
{
get { return (Double)GetValue(LeftProperty); }
set
{
SetValue(LeftProperty, value);
NotifyPropertyChanged("Left");
}
}
public Object Model
{
get { return (Object)GetValue(ModelProperty); }
set
{
SetValue(ModelProperty, value);
NotifyPropertyChanged("Model");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String aPropertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(aPropertyName));
}
}
Then, in my MODEL (ViewModel.cs), I create an ObservableCollection of this class:
public static readonly DependencyProperty ControlsProperty = DependencyProperty.Register("Controls", typeof(ObservableCollection<ControlDefinition>), typeof(MainWindow), new PropertyMetadata(null));
public new ObservableCollection<ControlDefinition> Controls
{
get { return (ObservableCollection<ControlDefinition>)GetValue(ControlsProperty); }
set
{
SetValue(ControlsProperty, value);
NotifyPropertyChanged("Controls");
}
}
Then, in the same MODEL, I initialize the collection and adds 4 dummy controls:
this.Controls = new ObservableCollection<ControlDefinition>();
this.Controls.Add(new ControlDefinition() { Top = 10, Left = 10, Model = "One" });
this.Controls.Add(new ControlDefinition() { Top = 50, Left = 10, Model = "Two" });
this.Controls.Add(new ControlDefinition() { Top = 90, Left = 10, Model = "Three" });
And I would have my VIEW (View.xaml) something like this:
<ItemsControl ItemsSource="{Binding Path=Controls}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="Beige" IsItemsHost="True" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Model, Mode=OneWay}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Top" Value="{Binding Path=Top, Mode=OneWay}" />
<Setter Property="Canvas.Left" Value="{Binding Path=Left, Mode=OneWay}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
NOTE that I show a "TextBlock" control inside my "DataTemplate", in lack of your control.
And I show the "Model" property (which I have defined as "String") in the TextBlock's "Text" property. You can assign the "Model" property to your control's "DataContext" property as in your example.
Hope it helps!

So, after all times of googling, asking and reading, here I found a solution:
There should be a model implementing INotifyPropertyChanged interface. DependencyProperties are not necessary. Here's an example:
public class MyItemModel: INotifyPropertyChanged
{
// INotifyPropertyChanged implementation
...
//
private Point _location;
public Point Location
{
get { return Location; }
set { _location = value; NotifyPropertyChanged("Location"); }
}
// any other fields
...
//
}
Then, say we have a model with a collection of MyItemModels:
public class MyModel: INotifyPropertyChanged
{
// INotifyPropertyChanged implementation
...
//
private ObservableCollection<MyItemModel> _myCollection;
public ObservableCollection<MyItemModel> MyCollection
{
get { return _myCollection; }
set { _myCollection = value; NotifyPropertyChanged("MyCollection"); }
}
}
Then, in XAML, we should use ItemsControl like this:
<ItemsControl x:Name="LayoutRoot" DataContext="{StaticResource model}"
ItemsSource="{Binding MyCollection}"
HorizontalAlignment="Left" VerticalAlignment="Top">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas x:Name="host"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid x:Name="item" Background="Transparent">
<Grid.RenderTransform>
<TranslateTransform X="{Binding Location.X}" Y="{Binding Location.Y}"/>
</Grid.RenderTransform>
// other stuff here
...
//
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Works like a charm :) Thanks everyone.

Related

Can't bind DependencyProperties from UserControl code behind to ViewModel

I'm trying to create a UserControl in my WPF project which I want should have a DependencyProperty that I can bind to in the parent. The project is written as MVVM and I'm using Caliburn micro.
I really want to write clean and maintainable code using MVVM, so I want my UserControls to utilize viewmodels as much as possible and code behind as little as possible.
The problem is that I'm unsuccessful in getting the binding between the parent and the UserControl viewmodel to work correctly.
MyUserControl:
public partial class MyUserControlView : UserControl
{
public MyUserControlView()
{
InitializeComponent();
// If no Datacontext is set, binding between parent property and textbox text works - one way only (set from parent)!.
// -
// If Datacontext is set to this, bindings with properties in MyUserControlView code behind works.
//DataContext = this;
// If Datacontext is set to MyUserControlViewModel, binding between MyUserControlViewModel and MyUserControlView works, but not with parent.
DataContext = new MyUserControlViewModel();
}
public string ProjectNumber
{
get { return (string)GetValue(MyUserControlValueProperty); }
set { SetValue(MyUserControlValueProperty, value); }
}
public static readonly DependencyProperty MyUserControlValueProperty =
DependencyProperty.Register("ProjectNumber", typeof(string), typeof(MyUserControlView), new PropertyMetadata(null, new PropertyChangedCallback(OnProjectNumberUpdate)));
private static void OnProjectNumberUpdate(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var view = d as MyUserControlView;
view.ProjectNumberText.Text = e.NewValue as string;
}
}
MyUserControl code behind:
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="In MyUserControl: " />
<TextBlock Text="{Binding ProjectNumber}" />
</StackPanel>
<TextBox Name="ProjectNumberText" Text="{Binding ProjectNumber, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
</StackPanel>
MyUserControl ViewModel:
public class MyUserControlViewModel : Screen
{
private string _projectNumber;
public string ProjectNumber
{
get { return _projectNumber; }
set
{
_projectNumber = value;
NotifyOfPropertyChange(() => ProjectNumber);
}
}
}
Parent view:
<StackPanel>
<local:MyUserControlView ProjectNumber="{Binding ParentProjectNumber}" />
<StackPanel Orientation="Horizontal">
<TextBlock Text="In parent: "/>
<TextBlock Text="{Binding ParentProjectNumber}" />
</StackPanel>
</StackPanel>
Parent ViewModel:
public class ShellViewModel : Screen
{
public ShellViewModel()
{
ParentProjectNumber = "Hello from parent!";
}
private string _parentProjectNumber;
public string ParentProjectNumber
{
get { return _parentProjectNumber; }
set
{
_parentProjectNumber = value;
NotifyOfPropertyChange(() => ParentProjectNumber);
}
}
}
I know I'm probably way off here, but I have no idea what to do to get the bindings to work correctly.
Is there a better way to bind between a DependencyProperty and a viewmodel? Can I put the DP in the viewmodel somehow?
Here is the entire project solution: https://github.com/ottosson/DependencyPropertyTest
don't change UserControl.DataContext from inside UserControl. it can and will create issues later.
use proper name for DP (ProjectNumberProperty and corresponding ProjectNumber) and add BindsTwoWayByDefault to metadata:
public partial class MyUserControlView : UserControl
{
public MyUserControlView()
{
InitializeComponent();
}
public string ProjectNumber
{
get { return (string)GetValue(ProjectNumberProperty); }
set { SetValue(ProjectNumberProperty, value); }
}
public static readonly DependencyProperty ProjectNumberProperty = DependencyProperty.Register
(
"ProjectNumber",
typeof(string),
typeof(MyUserControlView),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault)
);
}
fix bindings in xaml:
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="In MyUserControl: " />
<TextBlock Text="{Binding Path=ProjectNumber, RelativeSource={RelativeSource AncestorType=UserControl}}" />
</StackPanel>
<TextBox Text="{Binding Path=ProjectNumber, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, RelativeSource={RelativeSource AncestorType=UserControl}}" />
</StackPanel>
that should do it.
btw, "clean and maintainable code using MVVM" and "want my UserControls to utilize viewmodels as much as possible" sort of contradict each other.
also nothing wrong with code-behind in UserControls as long as that code handles only view functionality. for example: DataGrid source code contains 8000+ LoC

How to add a Rich Text Box to a Tab item so that can be added to a Tab Control in MVVM?

How to add a RichTextBox to a Tab item so that can be added to a Tab Control and display corresponding content in the RichTextBox dynamically in MVVM format.
ViewModel
private ObservableCollection<TabItem> TabControl()
{
ObservableCollection<TabItem> Tabs= new ObservableCollection<TabItem>();
return Tabs;
}
Controller
private void AddNewTabItem(string selectedItem)
{
try
{
System.Windows.Controls.RichTextBox richtextbox = new System.Windows.Controls.RichTextBox();
richtextbox.Name = "richtextbox" + selectedItem;
BrushConverter BC = new BrushConverter();
richtextbox.Background = (SolidColorBrush)(BC.ConvertFrom("#FF098BBB"));
richtextbox.Foreground = System.Windows.Media.Brushes.WhiteSmoke;
richtextbox.IsReadOnly = true;
TabItem m_tabItem = new TabItem();
m_tabItem.Header = selectedItem;
m_tabItem.Name = "tab" + selectedItem;
if (TabControl.Items.Count == 0)
{
TabControl.Items.Insert(0, m_tabItem);
TabControl.SelectedIndex = msgTracerTabControl.Items.Count - 1;
}
else
{
TabControl.Items.Insert(msgTracerTabControl.Items.Count - 1, m_tabItem);
TabControl.SelectedIndex = msgTracerTabControl.Items.Count - 2;
}
m_tabItem.Content = new System.Windows.Controls.RichTextBox();
m_tabItem.Content = richtextbox;
Tabs.add(m_tabItem);
}
catch (Exception EX)
{
}
}
View
<TabControl Grid.Column="1" Grid.Row="1" ItemsSource="{Binding TabControl}" }"/>
I have used this code and working fine and this is not in MVVM this is WAF Architecture in that i'm using MVVM concept.
You're not thinking MVVM. In a ViewModel you would not directly access UI elements but would rather set up bindings and data templates which would render your Viewmodels correctly. The correct approach is to have 2 viewmodels, 1 to act as a master and the second to act as the underlying DataContext for each tab.
A simple example would be something like this:
MainViewModel
public class MainViewModel : BindableBase
{
private int _tabSuffix;
public ObservableCollection<TextViewModel> TextTabs { get; set; } = new ObservableCollection<TextViewModel>();
public DelegateCommand AddNewTabCommand { get; set; }
public MainViewModel()
{
AddNewTabCommand = new DelegateCommand(OnAddNewTabCommand);
}
private void OnAddNewTabCommand()
{
TextTabs.Add(new TextViewModel()
{
Header = $"Tab #{_tabSuffix++}"
});
}
}
MainView
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Button Grid.Row="0" Content="Add new tab item" Command="{Binding AddNewTabCommand}"></Button>
<TabControl Grid.Row="1"
ItemsSource="{Binding TextTabs}"
IsSynchronizedWithCurrentItem="True">
<!-- Defines the header -->
<TabControl.ItemTemplate>
<DataTemplate DataType="{x:Type so44497239:TextViewModel}">
<TextBlock Text="{Binding Header}" />
</DataTemplate>
</TabControl.ItemTemplate>
<!-- defines the context of each tab -->
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type so44497239:TextViewModel}">
<RichTextBox Background="#FF098BBB" Foreground="WhiteSmoke" IsReadOnly="False" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
TextViewModel
public class TextViewModel : BindableBase
{
public string Header { get; set; }
public Brush BackgroundBrush { get; set; }
public Brush ForegroundBrush { get; set; }
public string Document { get; set; }
}
In this example, the main viewmodel has no knowledge of the View but merely adds items to it's own ObservableCollection. The TabControl itself, through the binding to TextTabs, adds it's own tab items and renders them using the ItemTemplate and ContentTemplate properties.
Download code here

Using bound data in DataTemplate

I have a CustomWindow.cs that I'm decorating using a DataTemplate, as there are a large number of content variations. As per MVVM, the window's DataContext is bound to a ViewModel
Ideally, some of these decorations would be populated using data from the ViewModel.
The structure I would like to achieve is something like the following:
<CustomWindow DataContext="{Binding Main, Source={StaticResource Locator}}">
<Content>
</CustomWindow>
The DataTemplate may look something like:
<DataTemplate DataType="{x:Type CustomWindow}">
<ContentPresenter Content="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type viewModels:HmiViewModelBase}}}">
<ContentPresenter Content="{Binding Content}"/>
</ContentPresenter>
</DataTemplate>
I realise that the double definition of Content in ContentPresenter wouldn't work but can't think of an alternative.
How would I achieve something like this?
I feel like this would be a common issue.
first of all welcome to SO. Please look at the next concept: 1) One main view model contains a number of models in some observable collection (or based on binding and properties). 2) Each model in observable collection has its own logic and is supposed to be presented in some original way. 3) The main view model is presented by main view (let say list box).
4) Each mode inside the observable collection of the main view model is presented by content control which will select some original content template for its content (which is a model inside the observable collection). 5)Data template based on the model type can use every wpf control (or user custom control you made) and present data. Here is the code:
1. XAML code:
<Window x:Class="DataTemplateSOHelpAttempt.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:dataTemplateSoHelpAttempt="clr-namespace:DataTemplateSOHelpAttempt"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<dataTemplateSoHelpAttempt:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type dataTemplateSoHelpAttempt:OrangeObject}">
<TextBlock Text="{Binding Description}" Background="Orange"></TextBlock>
</DataTemplate>
<DataTemplate DataType="{x:Type dataTemplateSoHelpAttempt:GreenObject}">
<TextBlock Text="{Binding Description}" Background="GreenYellow"></TextBlock>
</DataTemplate>
<DataTemplate DataType="{x:Type dataTemplateSoHelpAttempt:BlueObject}">
<TextBlock Text="{Binding Description}" Background="CadetBlue"></TextBlock>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox ItemsSource="{Binding Objects}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<ContentControl Content="{Binding }"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</Grid></Window>
2. View model code:
public class MainViewModel:BaseObservableObject
{
public MainViewModel()
{
Objects = new ObservableCollection<BaseDataObject>(new List<BaseDataObject>
{
new BlueObject{Description = "Hello I'm blue object!!!"},
new OrangeObject{Description = "Hello I'm orange object!!!"},
new GreenObject{Description = "Hello I'm green object!!!"},
new OrangeObject{Description = "Hello I'm anoter orange object!!!"},
new BlueObject{Description = "Hello I'm another blue object!!!"},
new OrangeObject{Description = "Hello I'm another orange again object!!!"},
new GreenObject{Description = "Hello I'm another green object!!!"},
new OrangeObject{Description = "Hello I'm again another orange object!!!"},
});
}
public ObservableCollection<BaseDataObject> Objects { get; set; }
}
3. Models Code:
public abstract class BaseDataObject:BaseObservableObject
{
public abstract string Description { get; set; }
}
public class OrangeObject:BaseDataObject
{
private string _description;
public override string Description
{
get { return _description; }
set
{
_description = value;
OnPropertyChanged();
}
}
}
public class BlueObject:BaseDataObject
{
private string _description;
public override string Description
{
get { return _description; }
set
{
_description = value;
OnPropertyChanged();
}
}
}
public class GreenObject:BaseDataObject
{
private string _description;
public override string Description
{
get { return _description; }
set
{
_description = value;
OnPropertyChanged();
}
}
}
4. Basic INotifyPropertyChanged implementation (use 4.5 dotNet version for CallerMemberName):
public class BaseObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> raiser)
{
var propName = ((MemberExpression)raiser.Body).Member.Name;
OnPropertyChanged(propName);
}
protected bool Set<T>(ref T field, T value, [CallerMemberName] string name = null)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
OnPropertyChanged(name);
return true;
}
return false;
}
}
That's all, copy/past, debug and use. I'll be glad to help if you will have problems with the code. Please mark it as answered if the answer was helpful.
Regards,

WPF/Silverlight Bind Canvas to Collection of View Model Elements

Is it possible to use a DataTemplate to render a collection of points as a bunch of lines (with data binding and drag and drop)?
Here are the details:
I have multiple objects in my view model. These objects ultimately have locations on a canvas specified in absolute pixel coordinates. I need to be able to drag and drop these items around on the canvas and update their coordinates. Some objects are represented by a point, others are a collection of line segments. I'm using MVVM (Jounce). Should my view model expose a ObservableCollection<Shape> that somehow binds the coordinates? That feels wrong. Or is there a way I can use DataTemplates here to draw lines with points on the end of each line segment given a collection of line segments? Here is an example ViewModel:
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Jounce.Core.ViewModel;
namespace CanvasBindTest.ViewModels
{
/// <summary>
/// Sample view model showing design-time resolution of data
/// </summary>
[ExportAsViewModel(typeof(MainViewModel))]
public class MainViewModel : BaseViewModel
{
public MainViewModel()
{
var start = new PointView { X = 0, Y = 0 };
var middle = new PointView { X = 1132 / 2, Y = 747 / 2 };
var end = new PointView() { X = 1132, Y = 747 };
var lineView = new LineView(new[] { start, middle, end });
Lines = new LinesView(new[] { lineView });
}
public LinesView Lines { get; private set; }
}
public class LinesView : BaseViewModel
{
public ObservableCollection<LineView> Lines { get; private set; }
public LinesView(IEnumerable<LineView> lines)
{
Lines = new ObservableCollection<LineView>(lines);
}
}
public class LineView : BaseViewModel
{
public ObservableCollection<PointView> Points { get; private set; }
public LineView(IEnumerable<PointView> points)
{
Points = new ObservableCollection<PointView>(points);
}
}
public class PointView : BaseViewModel
{
private int x, y;
public int X
{
get { return x; }
set { x = value; RaisePropertyChanged(() => X); }
}
public int Y {
get { return y; }
set { y = value; RaisePropertyChanged(() => Y); }
}
}
}
Here is the View, which is a canvas wrapped in a ItemsControl with a background image. The view model coordinates are relative to the background image's unscaled size:
<UserControl x:Class="CanvasBindTest.MainPage"
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:viewModels="clr-namespace:CanvasBindTest.ViewModels"
mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400">
<UserControl.Resources>
<DataTemplate x:Key="SkylineTemplate" DataType="viewModels:LineView">
<ItemsControl ItemsSource="{Binding Points}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<!--I have a collection of points here, how can I draw all the lines I need and keep the end-points of each line editable?-->
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DataTemplate>
</UserControl.Resources>
<Grid d:DataContext="{d:DesignInstance viewModels:MainViewModel, IsDesignTimeCreatable=True}">
<ScrollViewer x:Name="Scroll">
<ItemsControl ItemsSource="{Binding Lines}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas>
<Canvas.Background>
<ImageBrush Stretch="Uniform" ImageSource="Properties/dv629047.jpg"/>
</Canvas.Background>
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ScrollViewer>
</Grid>
</UserControl>
Your LineView must be LineViewModel, it'll be more correct.
I describe the mechanism for points, for lines I think you will understand by yourself.
Main Control
Use ItemsControl.
ItemsControl.PanelControl must be Canvas.
ItemsSource - Your collection of PointWiewModel.
Make two DataTemplates for types PointWiewModel.
Make the PointView control and put it into the appropriate DataTemplate.
PointView control
Two way bind Canvas.X attached property to PointViewModel.X property.
Two way bind Canvas.Y attached property to PointViewModel.Y property.
Add logic of changing Canvas.X and Canvas.Y when you drag a PointView control.
Result
After that you could drag your (for example) PointVew control and the properties in your view model will be updated because of two way binding.
Suppose I understand correctly what do you want.
Added answers to the questions
Silverlight 5 supports it. That's mean all the items will be placed on the Canvas control. Some article about ItemsControl.
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas></Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
PointView is the second user control.
Note: I've described a way to draw an array of points with MVVM. You can drag each point on the canvas and receiving the new coordinates in your view model. (Maybe my description was a bit confusing on this stage so I've deleted LineViews from it)
In order to make a Lines, you have to connect your points. It'll be more difficult so I suggest you to make a variant with points only.
When you will be familiar with it, you can move your ItemsControl into templated control. Make your own ItemSource collection and drawing the Path by this points when they will change the position.
You can also search some opensource graph controls and see how they drawing curving lines by dots. Actually they usually do doing it with the Path like I have described.
Sorry, but I wouldn't write more because it'll became an article but not an answer)
P.S: It is interesting question, so If I have some free time I may be write an article. About templated controls you can read here.
It's absolutely disgusting how much XAML this takes. I'll look for a way to clean it up using styles and templates. Also, I need to draw the line to the center of the point, that shouldn't be hard. For now, below is what worked. I ended up created a Collection<Pair<Point, Point>> ViewModel to bind the "Line" collection. Otherwise I'm looking at the line point-by-point and can't draw a line since I can't find X2/Y2.
Thanks for the inspiration Alexander.
Here is the XAML:
<ItemsControl ItemsSource="{Binding Lines}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="viewModels:LineViewModel">
<ItemsControl ItemsSource="{Binding LineSegments}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl ItemsSource="{Binding Lines}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Line X1="{Binding Item1.X}" X2="{Binding Item2.X}" Y1="{Binding Item1.Y}" Y2="{Binding Item2.Y}" Stroke="Black" StrokeThickness="2"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ItemsControl ItemsSource="{Binding LineSegment}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Ellipse Canvas.Left="{Binding X}" Canvas.Top="{Binding Y}" Width="10" Height="10" Fill="Black">
<Ellipse.RenderTransform>
<TranslateTransform X="{Binding X}" Y="{Binding Y}"/>
</Ellipse.RenderTransform>
</Ellipse>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Here is the ViewModel:
namespace CanvasBindTest.ViewModels
{
/// <summary>
/// Sample view model showing design-time resolution of data
/// </summary>
[ExportAsViewModel(typeof (MainViewModel))]
public class MainViewModel : BaseViewModel
{
public MainViewModel()
{
var start = new PointViewModel {X = 0, Y = 0};
var middle = new PointViewModel {X = 30, Y = 10};
var end = new PointViewModel {X = 20, Y = 0};
var simpleLine = new LineSegmentsViewModel(new[] {start, middle, end});
Lines = new ObservableCollection<LineViewModel> {new LineViewModel(new[] {simpleLine})};
}
public ObservableCollection<LineViewModel> Lines { get; private set; }
}
public class LineViewModel : BaseViewModel
{
public LineViewModel(IEnumerable<LineSegmentsViewModel> lineSegments)
{
LineSegments = new ObservableCollection<LineSegmentsViewModel>(lineSegments);
}
public ObservableCollection<LineSegmentsViewModel> LineSegments { get; private set; }
}
public class LineSegmentsViewModel : BaseViewModel
{
public LineSegmentsViewModel(IEnumerable<PointViewModel> lineSegment)
{
LineSegment = new ObservableCollection<PointViewModel>(lineSegment);
Lines = new Collection<Tuple<PointViewModel, PointViewModel>>();
var tmp = lineSegment.ToArray();
for (var i = 0; i < tmp.Length - 1; i++)
{
Lines.Add(new Tuple<PointViewModel, PointViewModel>(tmp[i], tmp[i+1]));
}
}
public Collection<Tuple<PointViewModel, PointViewModel>> Lines { get; private set; }
public ObservableCollection<PointViewModel> LineSegment { get; private set; }
}
public class PointViewModel : BaseViewModel
{
private int x, y;
public int X
{
get { return x; }
set
{
x = value;
RaisePropertyChanged(() => X);
}
}
public int Y
{
get { return y; }
set
{
y = value;
RaisePropertyChanged(() => Y);
}
}
}
}

How do I bind IsChecked property of CheckBox within an ItemsControl?

I've got the following ItemsControl that gives me a check box for every database within the available collection. These checkboxes allow the user to select which ones to filter on. The databases to filter on are in a separate collection (FilteredDatabases). How exactly do I do this? I could add an InFilter property to the database item class. But, I don't want to start changing this code yet. The problem I can't get around in my head is the fact that I need to bind to a property that is not on the database item itself. Any ideas?
<ItemsControl ItemsSource="{Binding AvailableDatabases}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Name}" IsChecked="{Binding ???}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
// In view model
public IBindingList FilteredDatabases
{
get;
private set;
}
public IBindingList AvailableDatabases
{
get;
private set;
}
Bind CheckBox.Command to routed command instance
Bind routed command to method
Use IBindingList.Add and IBindingList.Remove methods
The following code illustrates what you are trying to do, in order to do this you are better off using ObservableCollection instead of as your collection object, if an ItemsControl is bound to it it will automatically update the UI when viewmodels are added and removed.
XAML:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ItemsControl Grid.Column="0" ItemsSource="{Binding AvailableDatabases}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Name}" IsChecked="{Binding IsChecked}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ItemsControl Grid.Column="1" ItemsSource="{Binding FilteredDatabases}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
View Models:
public class MainViewModel
{
private ObservableCollection<DBViewModel> _availableDatabases;
private ObservableCollection<DBViewModel> _filteredDatabases;
public ObservableCollection<DBViewModel> AvailableDatabases
{
get
{
if (_availableDatabases == null)
{
_availableDatabases = new ObservableCollection<DBViewModel>(new List<DBViewModel>()
{
new DBViewModel(this) { Name = "DB1" , IsChecked = true},
new DBViewModel(this) { Name = "DB2" },
new DBViewModel(this) { Name = "DB3" },
new DBViewModel(this) { Name = "DB4" },
new DBViewModel(this) { Name = "DB5" },
new DBViewModel(this) { Name = "DB6" },
new DBViewModel(this) { Name = "DB7" , IsChecked = true },
});
}
return this._availableDatabases;
}
}
public ObservableCollection<DBViewModel> FilteredDatabases
{
get
{
if (_filteredDatabases == null)
_filteredDatabases = new ObservableCollection<DBViewModel>(new List<DBViewModel>());
return this._filteredDatabases;
}
}
}
public class DBViewModel
{
private MainViewModel _parentVM;
private bool _isChecked;
public string Name { get; set; }
public DBViewModel(MainViewModel _parentVM)
{
this._parentVM = _parentVM;
}
public bool IsChecked
{
get
{
return this._isChecked;
}
set
{
//This is called when checkbox state is changed
this._isChecked = value;
//Add or remove from collection on parent VM, perform sorting here
if (this.IsChecked)
_parentVM.FilteredDatabases.Add(this);
else
_parentVM.FilteredDatabases.Remove(this);
}
}
}
View models should also implement INotifyPropertyChanged, I omitted it since it was not necessary in this particular case.

Resources