2

Maybe it is a simple question, but I can’t find the answer. I have three User controls that are different only with colour. There is code one of them:

<UserControl x:Class="SilverlightApplication14.NodePicture"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:SilverlightApplication14">

    <UserControl.Resources>
        <local:NodeViewModel x:Key="Children"  />
    </UserControl.Resources>
    <Grid x:Name="LayoutRootNodePicture" Height="100" Width="100"
          HorizontalAlignment="Center"  DataContext="{Binding Source={StaticResource Children}, Path=Children}" >
        <Canvas x:Name="ParentCanvas" Background="White" Width="100" Height="100" >
            <Rectangle Fill="Yellow" Stroke="Blue" Width="100" Height="100"   >
                </Rectangle  >

        </Canvas>
        <Image HorizontalAlignment="Center"
                       Source="add.png"
                       Stretch="Fill"
                       Width="16"
                       VerticalAlignment="Top"
                       Margin="0,0,2,2"
                       Height="16" MouseLeftButtonDown="Image_MouseLeftButtonDown">
        </Image>
            </Grid>
</UserControl>

How can I combine them into ObservableCollection Children?

public class NodeViewModel : INotifyPropertyChanged
    {

public ObservableCollection<NodeViewModel> Children
        {
            get { return _children; }
            set
            {
                _children = value;
                NotifyChange("Children");
            }
        }

        private void NotifyChange(string propName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propName));
        }
}

And how can I use then elements of this controls collection?

Is there a simple (or a right way ) way of doing this?

revolutionkpi
  • 2,632
  • 10
  • 45
  • 84
  • 1
    It is not clear what you are after. You said you have three UserControls as above where you set `DataContext="{Binding Source={StaticResource Children}, Path=Children}"` and you want to combine those three into the `Chilren` collection? What do you mean by "controls collection"? – bitbonk Sep 28 '11 at 06:13
  • Yes, I want to combine those three into the Chilren collection @bitbonk – revolutionkpi Sep 28 '11 at 06:21
  • 1
    You can't becaue it is a collection of `NodeViewModel` instances not a collection of `UserControl` instances. – bitbonk Sep 28 '11 at 06:48
  • 2
    I need to know answers to the two following questions before I can decide how to implement this control: 1) Can you add additional properties to the viewmodel? 2) How these three user controls are different in the xaml code? It would be easier if they were declared something like ``. – vortexwolf Sep 28 '11 at 08:45
  • 1) Yes, you can add additional properties. 2) As I have said at the top of this page: " they are different only with colour". @ vorrtex – revolutionkpi Sep 28 '11 at 08:52
  • @revolutionkpi I tried to know whether you have your 3 usercontrols as separate files or the division is more logical. Anyway I will write in my answer how it should be. – vortexwolf Sep 28 '11 at 18:29
  • I have separate 3 usercontrols in 3 separate files. I am looking forward to your reply. And it is very important how to create elements of this collection in triger invoke method, if it is possible)@vorrtex – revolutionkpi Sep 28 '11 at 18:40
  • I haven't seen full code so I don't know where you use triggers. But I'm sure that it must be asked as a separate question with detailed description. – vortexwolf Sep 28 '11 at 19:03
  • Also I want to say that if controls differ dramatically (not just by colors) it will require much more work to select a correct view. Here is how it is implemented in WPF: http://stackoverflow.com/questions/5309099/changing-the-view-for-a-viewmodel/5310213#5310213 – vortexwolf Sep 28 '11 at 19:16

1 Answers1

1

As far as I understood you right, you have 3 user controls which have names something like NodePicture, GreenNodePicture and BlueNodePicture. First of all, if the 3 controls differ to a very little degree, it would be better to have only one control which switches the color using some property value.

Let's suppose that your controls differ by the background color of the rectangle on the canvas. So I would change your control so:

<Grid x:Name="LayoutRootNodePicture" Height="100" Width="100"
      HorizontalAlignment="Center">
    <Canvas x:Name="ParentCanvas" Background="{Binding NodeColor}" Width="100" Height="100" >
    </Canvas>
    <Image HorizontalAlignment="Center"
                   Source="add.png"
                   Stretch="Fill"
                   Width="16"
                   VerticalAlignment="Top"
                   Margin="0,0,2,2"
                   Height="16" MouseLeftButtonDown="Image_MouseLeftButtonDown">
    </Image>
</Grid>

I've removed the Resources section because the view shouldn't create new view model objects, it should use an existing DataContext. You can see that the background color of the rectangle is based on the property NodeColor of the view model. Let's add this property to the view model:

public class NodeViewModel : INotifyPropertyChanged
{
    private SolidColorBrush _nodeColor;

    public SolidColorBrush NodeColor
    {
        get { return _nodeColor; }
        set
        {
            _nodeColor = value;
            NotifyChange("NodeColor");
        }
    }
    //...

And now if you want to display 3 controls with different color you should create 3 view models with different properties. Here is the example of the red, blue and green viewmodels:

public partial class MainPage : UserControl
{
    public MainPage()
    {
        InitializeComponent();
        var redBrush = new SolidColorBrush(Color.FromArgb(255, 255, 0, 0));
        var greenBrush = new SolidColorBrush(Color.FromArgb(255, 0, 255, 0));
        var blueBrush = new SolidColorBrush(Color.FromArgb(255, 0, 0, 255));

        this.DataContext = new MainViewModel
        {
            Nodes = new ObservableCollection<NodeViewModel>{
                new NodeViewModel 
                { 
                    NodeColor = redBrush,
                    Children = new ObservableCollection<NodeViewModel>{
                        new NodeViewModel { NodeColor = greenBrush, LeftOffset = 65, TopOffset = 10},
                        new NodeViewModel { NodeColor = greenBrush, LeftOffset = 55, TopOffset = 60}
                    }
                }, //red
                new NodeViewModel { NodeColor = greenBrush}, //green
                new NodeViewModel { NodeColor = blueBrush} //blue
            }
        };
    }
}

public class MainViewModel
{
    public ObservableCollection<NodeViewModel> Nodes { get; set; }
}

View models are translated into the views using data templates:

<ListBox ItemsSource="{Binding Nodes}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <local:NodePicture DataContext="{Binding}" />
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

I haven't used the Children property because I haven't understood where to use it. Maybe child nodes are displayed on the canvas. Anyway if it is important - you can provide additional information and I'll help with this.

Update:

The easiest way to draw child items on the canvas is to add the dependency property which updates the canvas when the collection is updated:

public partial class NodePicture : UserControl
{
    public NodePicture()
    {
        InitializeComponent();
    }

    public IEnumerable<NodeViewModel> ChildViewModels
    {
        get { return (IEnumerable<NodeViewModel>)GetValue(ChildViewModelsProperty); }
        set { SetValue(ChildViewModelsProperty, value); }
    }

    public static readonly DependencyProperty ChildViewModelsProperty =
        DependencyProperty.Register("ChildViewModels", typeof(IEnumerable<NodeViewModel>), typeof(NodePicture),
        new PropertyMetadata(null, (s, e) => ((NodePicture)s).UpdateCanvas()));

    private void UpdateCanvas()
    {
        this.ParentCanvas.Children.Clear();
        var items = this.ChildViewModels;
        if(items == null)
            return;

        var controls = items.Select(item=>
            {
                var e = new Ellipse{Width = 20, Height = 20};
                e.Fill = item.NodeColor;
                //Or using the data binding
                //BindingOperations.SetBinding(e, Ellipse.FillProperty, new Binding("NodeColor") { Source = item });
                Canvas.SetLeft(e, item.LeftOffset);
                Canvas.SetTop(e, item.TopOffset);
                return e;
            });

        foreach(var c in controls)
            this.ParentCanvas.Children.Add(c);
    }

Where the TopOffset and LeftOffset are the properties of the NodeViewModel class. After that you should set this property in the xaml code:

    <DataTemplate>
        <local:NodePicture DataContext="{Binding}" ChildViewModels="{Binding Children}" />
    </DataTemplate>

It won't work with the ObservableColelction class because i didn't handle the CollectionChanged event. Another approach - to use the ListBox control with the custom ItemsPanelTemplate and ListBoxItem ControlTemplate. But it is the much more complex solution.

vortexwolf
  • 13,967
  • 2
  • 54
  • 72
  • Thank you for your attention to this question. I would like to add my question: as you correctly said, child nodes should be displayed on the canvas on the Main Page. If it is possible , can you append your answer) @vorrtex – revolutionkpi Sep 28 '11 at 19:23
  • @revolutionkpi Not on the MainPage, but on the canvas of the NodePicture control. I've changed some classes and added the `Update` section at the end of the answer. – vortexwolf Sep 28 '11 at 20:00