WPF - MVVM DataTemplate load to memory for reuse - c#

I have a window which has several different DataTemplate that are load to a ContentControl based on a RadioButton (The RadioButton sends a command to the ModelView which sets the Content property of the ContentControl.
It works well, but now several views contain a "heavy" object (Eyeshot CAD viewer).
Switching to any of these view causes a delay (at this moment there's absolutely zero logic in the whole software other than the view/view model)
Is there a way to load the view and the heavy control to memory once and then reuse it when switching to its view? (The ViewModel of that view is currently a singleton but that doesn't help)
<DockPanel>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Top" Height="160" Margin="0,0,0,12">
... Removed for clarity
</StackPanel>
<ContentControl x:Name="Tabs" Content="{Binding SelectedTabViewModel}" Margin="0,12,0,12"/>
</DockPanel>

On your DataTemplate, you can set the attribute x:Shared="True", this will allow the framework to reuse the visual control (inside the datatemplate) for another ContentPresenter.
This doesn't load the component at starting, but, this reuse it once instantiated.

Related

Dynamic WPF form creation approaches

I'm about to create a dynamic WPF UI form from DataTable data. The screens would be fairly complex. They would contain textboxes, groupboxes, checkboxes, buttons, datagrids etc. Some of them visible, some hooked up event handlers and thing like that.
What approach of creating those dynamic screens would you choose considering performance impact and complexity requirements to write and maintain source code. Please note that this code will run a LOT so it must be efficient and blazing fast. I'm considering these options:
Create Controls in code, assemble them to a tree and use the tree (Grid control) as a root element for a WPF form.
1.a) Create a XAML via XAMLReader from that screen object tree and Load it via XAMLReader inside WPF Form. Creating XAML would seem redundant to me since I can use the built tree as a Content for WPF form directly.
Use XMLDocument class to create tags, obejcts and their atributes. Create a XAMLlike that and then load that XAML in WPF form.
Thanks,
Michal
Consider displaying your form in a listview and creating a DataTemplate for each of your form fields textboxes, groupboxes, checkboxes, buttons, datagrids etc.
<ListBox ItemsSource="{Binding DataFormFields}"
<DataTemplate DataType="YourTextClass">
<StackPanel>
<TextBlock Text="{Binding LabelText}" />
<TextBox Text="{Binding ValueText}" />
</StackPanel>
</DataTemplate>
<DataTemplate DataType="YourCheckClass">
<StackPanel>
<CheckBox Content="{Binding LabelText}"
IsChecked="{Binding Checked}"/>
</StackPanel>
</DataTemplate>
For more on DataTemplates see
https://learn.microsoft.com/en-us/dotnet/framework/wpf/data/data-templating-overview
Each data template should be associated with a one of your form fields classes, using the DataType attribute, this will cause the listbox to automatically use the correct DataTemplate.
For more details:
https://learn.microsoft.com/en-us/dotnet/api/system.windows.datatemplate.datatype?view=netframework-4.7.2

WPF MVVM panel toggling with chart control taking too long

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

How to translate ViewModel to Canvas in MVVM and WPF

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.

Using Usercontrol inside Datatemplate

On a project I have started using Caliburn.Micro.
Now I want to split a large UserControl into several smaller UserControls, so that I can use them inside of DataTemplates. EisenbergEffect suggests to do so in this answer
The ViewModels for those are already there, as I modelled the hierachical data as ObservableCollection<SubViewModel>.
Now I thought I just have to create the suiting Views as UserControls for those SubViewModels.
My view is a Master-Details view. I want to present a list of Computers and in the Details View I want to have the HardwareComponents of those.
<ListView x:Name="ComputerViewModels">
<ListView.ItemTemplate>
<DataTemplate>
<ContentControl x:Name="HardwareComponentViewModel" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I have included Debug output as suggested here: Debug Logging
And I do not get any output about my HardwareComponentViewModel except
Action Convention Not Applied: No actionable element for set_HardwareComponentViewModel
The HardwareComponentViewModels already get created by the time the ComputerViewModels get created.
I already use the ContentControl - way on other parts of the application and it works very well there (getting the Content using IOC injected into the outer ViewModel).
But it does not seem to fit for the DataTemplate part (which is not very surprising, though). The SubViewModels are derived from Screen, as the outer ViewModels.
How can I use the new UserControl as DataTemplate?
Take a close look at EisenbergEffect's answer. It's explicitly stated, that conventions don't work inside DataTemplate, so if you have a complicated template, it's better to move it out to a separate UserControl, inside which conventions will work again.
That means you have to explicitly bind the model inside the template:
<ListView x:Name="ComputerViewModels">
<ListView.ItemTemplate>
<DataTemplate>
<ContentControl cal:View.Model="{Binding HardwareComponentViewModel}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
That should do it. Either that or cal:Model.Bind="{...}", I can never remember the difference and use case. Now inside the bound UserControl (HardwareComponentView, I presume) the conventions between view and the view model should work as usual.

Changing Main Viewing Area

I'm wondering how to go about creating different views in the main window when a button is pressed. I'm not sure of the correct terminology, so that has hampered my google fu.
I'm thinking that the main viewing area would be a content control, that I could change when a event happens. I made a small drawing to help illustrate my idea / thought.
Any input will be appreciated. Thanks!
It would be really easy to implement this senario using MVVM approach....
Make a ViewModel for you MainView. Then Define Properties of the ViewModels of your UserControls
For Example You have Two UserControl as FirstView and SecondView then make a properties in your viewmodels as ViewToLoadProperty of the type ViewModel (usually called as ViewModelBase)
Set bindings as
<!-- Panel For Hosting UserControls -->
<Border Grid.Column="2">
<ContentControl Name="userControlContentControl"
Content="{Binding Path=ViewToLoadProperty,
}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type ViewModelLayer:FirstViewModel}">
<ViewLayer:FirstView/>
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModelLayer:SecondViewModel}">
<ViewLayer:SecondView />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</Border>
<!-- Panel For Hosting UserControls -->
Then when you click the button Use a command to set the respective ViewModel Intance to this(ViewToLoadProperty) property...(Use RelayCommannds or something like it)
DataTempates would do the rest of the job by selecting the right View according to the right type of ViewModel
YOu can use MVVMLight toolkit if you are implementing MVVM Pattern.. :)
On the right you could have a frame. Then the button would bind a different page or user control to the content of that frame.

Categories

Resources