0

I am working on a small state chart editor. I try to implement it in the MVVM pattern.

What I got so far:

My main view

<UserControl.Resources>
    <vm:StateChartViewModel x:Key="StateChartViewModel"/>
    <DataTemplate DataType="{x:Type vm:StateViewModel}">
        <views:StateWidget/>
    </DataTemplate>
    <DataTemplate DataType="{x:Type vm:TransitionViewModel}">
        <views:TransitionWidget/>
    </DataTemplate>
</UserControl.Resources>


<Canvas Background="Transparent"
        DataContext="{StaticResource StateChartViewModel}">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="MouseLeftButtonDown">
            <command:EventToCommand Command="{Binding  CreateStateCommand}" PassEventArgsToCommand="True"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>

    <ItemsControl Canvas.Left="0"
                  Canvas.Top="0" 
                  ItemsSource="{Binding Transitions}">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <Canvas IsItemsHost="True"/>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemContainerStyle>
            <Style TargetType="ContentPresenter">
                <Setter Property="Canvas.Left" Value="0" />
                <Setter Property="Canvas.Top" Value="0" />
            </Style>
        </ItemsControl.ItemContainerStyle>
    </ItemsControl>

    <ItemsControl Canvas.Left="0"
                  Canvas.Top="0"
                  ItemsSource="{Binding States}">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <Canvas IsItemsHost="True"/>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemContainerStyle>
            <Style TargetType="ContentPresenter">
                <Setter Property="Canvas.Left" Value="0"/>
                <Setter Property="Canvas.Top" Value="0"/>
            </Style>
        </ItemsControl.ItemContainerStyle>
    </ItemsControl>

</Canvas>

The DataTemplates which I have set for the StateViewModel and the TransitionViewModel are views of these viewModels

StateView (StateWidget)

<UserControl.Resources>
    <vm:StateViewModel x:Key="StateViewModel"/>
</UserControl.Resources>
<Grid x:Name="XMainGrid"
      DataContext="{StaticResource StateViewModel}">
    <Rectangle Fill="CornflowerBlue" 
               RadiusX="5" RadiusY="5" 
               ClipToBounds="True">
    </Rectangle>
    <StackPanel Orientation="Horizontal"  Margin="10">
        <TextBox Text="{Binding Path=State.Name}" 
                 Background="Transparent"/>            
        <Polygon Points="0,0 20,0 10,10" 
                 Fill="DarkSlateGray" 
                 Margin="10,5,0,0"/>
    </StackPanel>
</Grid>

TransitionView (TransitionWidget)

<UserControl.Resources>
    <vm:TransitionViewModel x:Key="TransitionViewModel"/>
</UserControl.Resources>
<Canvas DataContext="{StaticResource TransitionViewModel}">
    <Line X1="{Binding SourceState.Position.X}"
          Y1="{Binding SourceState.Position.Y}"
          X2="{Binding TargetState.Position.X}"
          Y2="{Binding TargetState.Position.Y}"
          Stroke="DarkGreen"
          StrokeThickness="4"
          />
</Canvas>

In the StateChartViewModel I have 2 ObservableCollections with StateViewModels and TransitionModels

    private ObservableCollection<StateViewModel> _states;
    public ObservableCollection<StateViewModel> States
    {
        get
        {
            if (_states == null)
                _states = new ObservableCollection<StateViewModel>();

            return _states;
        }
    }

    private ObservableCollection<TransitionViewModel> _transitions;
    public ObservableCollection<TransitionViewModel> Transitions
    {
        get
        {
            if (_transitions == null)
                _transitions = new ObservableCollection<TransitionViewModel>();

            return _transitions;
        }
    }

    private RelayCommand<MouseEventArgs> _createStateCommand; 
    public ICommand CreateStateCommand
    {
        get
        {
            if (_createStateCommand == null)
                _createStateCommand = new RelayCommand<MouseEventArgs>(AddNewState);

            return _createStateCommand;
        }
    }

    private void AddNewState(MouseEventArgs e)
    {
        var newState = new StateViewModel();
        _states.Add(newState);
    }

My problem is that the canvas in the main view doesn't show any of that items when I add StateViewModels to States aswell with the transitions. When adding SateWidgets directly to the canvas' children they are appear in the canvas. But I think there have to be a solution without doing it manually.

Edit

The StateView (StateWidget) and the TransitionView (TransitionWidget) should both be displayed in the same canvas! StateViewModels should be displayed as StateWidgets and TransitionViewModels as Transitionwidgets.

  MainCanvas

  /--------------\    TransitionWidget    /--------------\
  | StateWidget1 |------------------------| StateWidget2 |
  \--------------/                        \--------------/
Zarzan
  • 31
  • 1
  • 6
  • Hi you are both setting datacontext on your StateView and your TransistionView, and at the sametime you have A collection of these stored in a different vm. Remove the datacontext from the StateView and TransistionView, so the items in the lists are set as datacontext. – Stígandr Sep 17 '14 at 09:54

1 Answers1

1

In your AddNewState method in your vm OnPropertyChanged("States") is redundant, as OC notifies the ui when you add or remove items. But you have something wrong in your views there:

StateView

<Grid x:Name="XMainGrid">
   <Rectangle Fill="CornflowerBlue" 
           RadiusX="5" RadiusY="5" 
           ClipToBounds="True">
   </Rectangle>
   <StackPanel Orientation="Horizontal"  Margin="10">
       <!-- Use twoway if you want to your binding to post back to your vm -->
       <TextBox Text="{Binding Path=State.Name, Mode=TwoWay}" 
                Background="Transparent"/>            
       <Polygon Points="0,0 20,0 10,10" 
                Fill="DarkSlateGray" 
               Margin="10,5,0,0"/>
   </StackPanel>
</Grid>

TransitionView:

<Canvas>
   <Line X1="{Binding SourceState.Position.X}"
      Y1="{Binding SourceState.Position.Y}"
      X2="{Binding TargetState.Position.X}"
      Y2="{Binding TargetState.Position.Y}"
      Stroke="DarkGreen"
      StrokeThickness="4"/>
</Canvas>

There now you are inherhiting the datacontext from the list items, rather than creating a new viewmodel as datacontext for each item in the list, which I think is your issue here. BTW These ViewModels for the two mentioned views, are they datamodels? Be sure to implement INotifyPropertyChanged in all nested classes. Have you considered using a ViewModelLocator BTW? I'm quite fond of GalaSoft MVVM Light.

Cheers

Stígandr
  • 2,874
  • 21
  • 36
  • thanks for your answer. Of course, the OnPropertyChanges call is redundant. I tried your changes, but this doesn't change anything. The ViewModels have a property to the datamodels and all ViewModels extends my BaseViewModel which implements the INotifyPropertyChanged. Where do the views get their data from if I don't set the dataContext? – Zarzan Sep 17 '14 at 10:43
  • @Zarzan Great to to see you do things so nice as you describe there, always good to hear! :) Unfortunately I'm in a bit of a rush here, so I can't recreate your project right now. [There is a similar case here](http://stackoverflow.com/a/5914970/2285592). The datacontext should be inherhited from your StateChartViewModel, or well in your itemscontrol each item will be given the datacontext of the item in the list. – Stígandr Sep 17 '14 at 11:19
  • I edited my question a bit so that it should be clearer what want. I thought the ViewModelLocator is for displaying different ViewModels but not at the same time? Or is it also useful in my case? – Zarzan Sep 17 '14 at 13:18