Reputation: 77
I am new to WPF and MVVM, and would appreciate help with the following problem.
I want to create an application in which a user specifies – through a dialog - how he would like to layout N number of chart objects on a page, and the application shows him this layout on a canvas. When satisfied with the layout he sees in the canvas, the user persists it for later use.
All chart objects can be visualized as rectangles. User can also define a header, which too is a rectangle.
A typical layout could be the header at the top of the page, below which are three charts side-by-side. User would be able to specify this layout as well as dimensions and location of each child in a dialog, and then hit the ‘Apply’ button expecting to see this specification in graphical form on the canvas.
In my view model I would have a tree where the parent is the canvas, having one child of type header, and 3 children of chart type.
The user might not like what he sees, and make changes in the dialog which would then effect changes in the view model.
I kind of understand the View-ViewModel interaction between the dialog and the view model. But don’t know how to implement the Canvas-ViewModel interaction. Meaning that when the user requests in the dialog say a header rectangle of a given size at a given coordinate, I know how to add that header object in the tree in the view model, but I do not know how to then update the canvas from the ViewModel's tree. How would the canvas get drawn to reflect the object tree in the viewmodel, and then get re-drawn each time the viewmodel changes (as a result of user's interaction with the dialog)?
Upvotes: 0
Views: 3972
Reputation: 170
One option is to add the viewmodels to a collection, and then bind those to an ItemsControl. If you provide the appropriate datatemplates in the XAML, the views are automatically bound to the data. The Itemscontrol I have looks like this:
<ItemsControl x:Name="WorksetPresenter"
ItemsSource="{Binding ElementName=RootWindow, Path=TableauItems}"
>
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type viewModels:AnalysisViewModel}">
<wg:AnalysisView DataContext="{Binding DescriptiveAnalysis}"/>
</DataTemplate>
<!-- more datatemplates for more view/viewmodel pairs -->
</ItemsControl.Resources>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
TableauItems is an ObservableCollection<>. As soon as an ViewModel is added to the collection, it is rendered on the Canvas according to the View specified in the datatemplate. For positioning you can use e.g. the Canvas.Left and Canvas.Top properties (mind the alignment!), or a rendertransform.
Upvotes: 2
Reputation: 6547
You shouldn't store graphical settings such as size and coordinates of your controls in your viewModel.
If I were you I would approch this a bit differently.
In the View
, Use DragAndDrop operations on the canvas to let the user change the location of the charts and header.
You can use GridSplitter
to make them user-resizable
Then, when user hits Apply
, Save the canvas object using XamlWriter.Save method
When you need it for later use, load it using XamlReader.Load method
In your ViewModel
have a command that gets the canvas as a parameter and handles the Save
operation.
view:
<Canvas x:Name="mainCanvas">
<StackPanel>
<TextBlock Text="My Header ..."/>
<!-- Charts goes here .... -->
<Button Content="Apply">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding ApplyCommand}" CommandParameter="{Binding ElementName=mainCanvas}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</StackPanel>
</Canvas>
view model:
public class MainWindowViewModel
{
public MainWindowViewModel()
{
ApplyCommand = new DelegateCommand<Canvas>(canvas =>
{
string userLayout = XamlWriter.Save(canvas);
// save userLayout for later use ...
});
}
public DelegateCommand<Canvas> ApplyCommand { get; set; }
}
Hope this helps
Upvotes: -1
Reputation: 14334
If the application specifically deals with changes in layout and layout information is the data you are presenting, then putting layout information in your view model is certainly appropriate. However, simple presentation information does not belong in your view model.
For that you need a different solution. Consider this. If I need to locate view model template location on screen, how do I do it? My view model cannot know about the visual tree! Bugger. To solve this, I tag elements with attached properties and use a custom layout behavior or control to query the attached properties.
This is very similar to how jQuery allows a javascript programmer to grab DOM elements from a web page.
Upvotes: 0