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)?
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.
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.
Example
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
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.
Related
Based on this mechanism, I created a dialog window of which I can dynamically assign the content by a <ContentPresenter Content="{Binding .}">
The content I want to assign is an user control with a corresponding ViewModel. This works as I can render the DialogView in other usercontrols
<DataTemplate DataType="{x:Type ViewModels:DialogViewModel}">
<Views:DialogView/>
</DataTemplate>
)
However, in the DialogWindow, DialogView cannot be rendered but instead, only the string representation of DialogViewModel is visible. What might be the reason why I cannot render the view of contentpresenter's content?
Any help is much appreciated!
Thanks in advance
Where did you define the Data Template? It sounds like you are creating them as Window resources, and did not include it in your DialogWindow. If you're defining them as Window resources, the Data Template definition needs to be included on every Window you want to render this way. If the ViewModel/View pair is global to the application, it is easier to just define it in the App.xaml where it will be picked up by any Window or UserControl throughout the application.
I want to program a dynamic Detail View. Like a user clicked on an Item then he sees the detail view. Now he sees all Values, but when he only want to see a few values, he click on a config Button in this view and a second view opens where he can select and unselect all types of values. Like he dont want to see the Description, the he deselect it in the second view, and it´s no longer visible in the first view.
The only way for me to implement something like this is to programm a Function which generates the first view. The view would be a UI-Element. Which is then returned to the Windows where the UI-Element is set a child of an Element on the Window. But I think this isn´t a good way. How do you would solve this problem?
Thanks for every hint :)
If I understand well you want
List -> Details -> MoreDetails/Edit
Depending on what platform you are creating is a bit different but the idea is the following:
<ItemsControl ... x:Name="ItemsList" ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Tapped">
<core:InvokeCommandAction
Command="{Binding Datacontext.ShowItemDetails, ElementName=ItemsList}" CommandParameter="{Binding}"/>
</core:EventTriggerBehavior>
</Interactivity:Interaction.Behaviors>
</Grid>
</DataTemplate>
</ItemsControl>
Now when you tap the grid you can show a PopupControl with the details and the DataContext:
public Command<ItemClass> ShowPopup
{
get
{
return new Command<ItemClass>((i)=>
{
//Create the Popup
});
}
}
In case you are not using MVVM you can add the Command in code behind and place in the page contructor this.DataContext = this; and place the previous command there.
And now create a control for the content of the popup, bind the properties to the item details, now add another behavior with a command in that control and unhide the controls for edit mode or more details mode
You could have for each detail item a property bool ItemXIsVisible which is bound to a checkbox in the config view, and to the IsVisible property of the X control in the detail view?
I explain my issue as I'm quite new to UI design :
I have a main View which displays a TreeView on its left part. When an element is selected I'd like to show a description of the Item on the right on the same window. The design of this description depends on the nature of the Item.
So I created a View per Item Type corresponding to the different possible design.
Now When I click on the TreeView I have no idea how to show the corresponding view on the right of the same window. (I'm not asking about catching the event, just displaying a view within another view, like if I dynamically plotted a control).
Is it possible ? If not what kind of approach would you suggest ?
Many Thanks.
This seems like a great candidate for a Data Template.
Basically, create a content presenter and bind its content property to the TreeView's SelectedItem property. Now, create data templates for each of your types (using the DataType property) in the ContentTemplate property.
Now, the correct data template will be chosen with the correct data whenever you select something in your tree view.
As far as a separate dispatcher goes, I'm not sure, but I'm also not sure what scenario would require one.
More information can be found in this SO question.
Sample:
<ContentPresenter Content="{Binding Path=SelectedItem, ElementName=TreeView}">
<ContentPresenter.ContentTemplate>
<DataTemplate DataType="{x:Type Type1}">
<!-- Bunch of stuff-->
</DataTemplate>
<DataTemplate DataType="{x:Type Type2}">
<!-- Bunch of stuff-->
</DataTemplate>
</ContentPresenter.ContentTemplate>
</ContentPresenter>
I have a WPF interface that has a panel used for displaying details about the particular option you have selected from a button bar. Eg if you click the Info button, the detail pane populates with a InfoDetailUserControl. If you click the Graph button, it populates with a GraphDetailUserControl.
The way I am doing this is to define each detail panel as a UserControl. I then have a ViewModel for each UserControl that drives the data. The detail panel is represented by a ContentControl and to display the relevant panel, I set the content of this to the ViewModel representing the UserControl I want to display. I then have a number of DataTemplates that map a ViewModel to a UserControl, so that when you add the VM to the ContentControl, it looks up the datatemplate for that type and displays the relevant UserControl.
Example datatemplates.
<DataTemplate DataType="{x:Type RunResults:SimpleCalcInfoResultViewModel}">
<Views:SimpleCalcInfoResult />
</DataTemplate>
<DataTemplate DataType="{x:Type RunResults:TradeResultViewModel}">
<Views:TradeResult />
</DataTemplate>
<DataTemplate DataType="{x:Type RunResults:GraphResultViewModel}">
<Views:GraphResult />
</DataTemplate>
<DataTemplate DataType="{x:Type RunResults:NoResultViewModel}">
<Views:NoResult />
</DataTemplate>
This all works fine, but the problem is that every time you change the content of the detail panel, you supply it the ViewModel class and it then on-the-fly looks up the datatemplate that matches the VM type and creates an instance of that UserControl. It then discards that UserControl when you switch to a different type. Hence if you keep switching between Info and Graph view for example, it keeps recreating the GraphUserControl every time you go back to it, it doesn't cache it from the first load and just redisplay the same view again.
The problem I have is that the Graph UserControl takes 3-4 seconds to initialise, in the InitializeComponent() call. I'm assuming this is just the WPF toolkit chart control being slow but it means the user must wait 4 seconds every time they go back to the Graph view, which is not ideal.
Is there a way I can either easily cache the first UserControl created so it only ever goes through that initialisation once or is there a way I can simply speed up the loading of the Chart control?
Many thanks
I am trying to host a user control inside my main window but I'm having a hard time deciding how to implement it into my view model.
First, I created a separate view model for the user control and used a data template to map it to the control before hosting it inside a ContentControl:
Data template:
<DataTemplate DataType="{x:Type viewModels:TaskbarIconViewModel}">
<tb:TaskbarIcon/>
</DataTemplate>
XAML:
<ContentControl Content="{Binding TaskbarIconViewModel}"/>
If I were to use this setup, how would I call methods on the user control and how would I set the properties of the user control, either in XAML or the view model?
Secondly, I had the idea to hold the user control in the view model and then bind it to a ContentControl once again. this seems wrong to me and against MVVM. Is this right? Is it acceptable to hold controls inside of a view model?
In the view model:
public TaskbarIcon TaskbarIcon { get; set; }
XAML:
<ContentControl Content="{Binding TaskbarIcon}"/>
What's the best course of action here?
Storing UserControl(s) in the ViewModel: a big no-no? YES
The ViewModel shouldn't be aware of the view.
What you're talking about is what PRISM's regions do, don't reinvent the wheel ;)