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.
Related
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.
I know there are a lot of questions about WPF navigation, for application developed with MVVM pattern, and I have read tens and tens of answers but I'm missing probably something.
I started building an application following Rachel's article here. All works just fine, there's an ApplicationView Window with this XAML:
<Window x:Class="CashFlow.ApplicationView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:CashFlow.ViewModels"
xmlns:v="clr-namespace:CashFlow.Views"
Title="ApplicationView" Height="350" Width="600" WindowStartupLocation="CenterScreen">
<Window.Resources>
<!--Here the associations between ViewModels and Views-->
<DataTemplate DataType="{x:Type vm:HomeViewModel}">
<v:HomeView />
</DataTemplate>
</Window.Resources>
<!--Define here the application UI structure-->
<DockPanel>
<Border DockPanel.Dock="Left" BorderBrush="Black" BorderThickness="0,0,1,0">
<ItemsControl ItemsSource="{Binding PageViewModels}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Name}"
Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding}"
Margin="2,5" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
<ContentControl Content="{Binding CurrentPageViewModel}" />
</DockPanel>
The ApplicationViewModel, that is set as DataContext for this window when the application starts, maintains an ObservableCollection of my ViewModels. Thanks to data templates, it's possible to associate every view with its viewmodel, using a ContentControl to render the views. Navigation in this case is accomplished with a "side bar" of buttons, binded to ApplicationViewModel commands that perform the changes of CurrentPageViewModel object.
I'm wondering how I can perform navigation without the presence of that sidebar of Buttons. Having only the Content control, I should be able to change the CurrentPageViewModel from the others viewmodel? Probably the answer will be very trivial, but I can't see that right now.
Your top level homeviewmodel can orchestrate navigation via an eventbus pattern. To use eventbus, you would inject an object that tracks objects that want to be notified of events. Then when a view model raises an event, the homeviewmodel receives it and performs the currentpageviewmodel assignment that will navigate you to the next viewmodel.
Ex:
Messenger defines two methods - RegisterForEvent<IEvent>(ViewModel aViewModel), and RaiseEvent(IEvent event).
So you would define a function to subscribe to the events -
HomeViewModel.cs
...
void SubscribeForEvents() {
Messenger.RegisterForEvent<NavigationEvent>(this);
}
Then you inject the Messenger into your other view models, and from those view models, raise the event:
Messenger.RaiseEvent(new NavigationEvent { TargetViewModel = new TargetViewModel() });
Where the event is something like
public class NavigationEvent : IEvent {
ViewModel TargetViewModel { get;set;}
}
C Bauer is right with what you are missing. I found in order to switch the data context, you'll need a messenger service to flag your "applicationviewmodel" to switch its data context. A good discussion with the steps you need are spelled out in a discussion here.
Register the message to be received in your applicationviewmodel, then handle the data context switch in your receive message function.
Also, this might be true or not, but I had to use 1 window, with multiple user controls as opposed to multiple windows if I wanted to have 1 window showing at all times. Lastly, I followed Sheridan's example and defined my data templates in my app.xaml as opposed to the window itself.
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'm developing a .NET 4.0 application using PRISM and MVVM, as well as WPF.
I currently have a shell subdivided in regions, with views inserted in them. When the user clicks on a button in one of the views, I would like a custom-made modal dialog to be displayed on top of all the views, but still within the same shell.
I looked at the StockTrader RI example and their implementation of the RegionPopupBehavior. Basically, they created a dependency property which allows them to define regions with a specific, custom-made behavior. The behavior is the one in charge of handling it's associated view's rendering, hence displaying it as a popup window.
The only downside to this approach is that all the other views are still active, so the popup isn't modal. I guess this can be resolved by manually disabling all un-needed regions in the shell, but I'm not sure how "clean" this is.
I was wondering if there were a better and simpler approach to displaying modal pop-up views in Prism ?
You might be interested in a custom PopupUserControl I have posted on my blog that behaves like that.
Usually I use it like this:
<local:PopupPanel
Content="{Binding PopupContent}"
local:PopupPanel.PopupParent="{Binding ElementName=SomeParentPanel}"
local:PopupPanel.IsPopupVisible="{Binding IsPopupVisible}">
<local:PopupPanel.Resources>
<DataTemplate DataType="{x:Type local:SomeViewModel}">
<local:SomeView />
</DataTemplate>
<DataTemplate DataType="{x:Type local:DifferentViewModel}">
<local:DifferentView />
</DataTemplate>
</local:PopupPanel.Resources>
</local:PopupPanel>
Although you can also just write the Content in the popup instead of binding the Content property
<local:PopupPanel
local:PopupPanel.PopupParent="{Binding ElementName=SomeParentPanel}"
local:PopupPanel.IsPopupVisible="{Binding IsPopupVisible}">
<Border BorderBrush="Blue" BorderThickness="2">
<local:MyUserControl />
</Border>
</local:PopupPanel>