I am developing plain WPF application on top of .NET 5.0.
I have navigation mechanism implemented based on DataTemplate(s)
<DataTemplate DataType="{x:Type viewmodels:FirstViewModel}">
<views:FirstView />
</DataTemplate>
<DataTemplate DataType="{x:Type viewmodels:SecondViewModel}">
<views:SecondView />
</DataTemplate>
and CurrentViewModel
<ContentControl Grid.Row="1" Content="{Binding CurrentViewModel}" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" />
You can imagine there are navbar buttons changing the CurrentViewModel property.
Besides that I use Dependency Injection which provides all required services (including viewmodels).
Both FirstViewModel and SecondViewModel are singletons.
FirstView and SecondView are UserControl(s).
The problem:
Whenever I navigate between UserControls they re-render. This is very inconvinient, from performance perspective and application usability.
Let's say both UserControls have grids with searching features and more. I would like user to be able to navigate back to the same state which he left on the control (including focused elements and so on). Having singleton viewmodel allows to preserve state/values of the properties but it doesn't solve the problem of control being re-rendered whenever I navigate. I clearly see that each time FirstView constructor is being called on which it InitializeComponent() and later calls OnRender().
Grids have asynchronous Sources.
<DataGrid Style="{DynamicResource DataGridStyle}" ItemsSource="{Binding ActiveSearchResults, IsAsync=True}" AutoGenerateColumns="False" CanUserSortColumns="True" CanUserAddRows="False">
.
.
.
</DataGrid>
I don't want grid to be re-rendered each time I navigate and elements to lose their focus. When I navigate I also see the grid flikering.
Can you please suggest option I have?
Is it possible to have singleton UserControl?
Is it possible to have static property with a cached state?
Related
I have an TabControl bound to a Dictionary and has a custom control as it's ContentTemplate. The custom control has a custom dependency property Schedules and it's DataContext is bound to a ViewModel, Here is how it look like:
Main control:
<TabControl Grid.Row="1" ItemsSource="{Binding Schedules}">
<TabControl.ContentTemplate>
<DataTemplate>
<TabControl >
<TabItem Header="Scheduled flights">
<views:MyViewer Schedules="{Binding Value}"/>
</TabItem>
</TabControl>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
In MyViewer, I also have an DataGrid that I want it to be bound the Schedules passed from the TabControl, but in the same time MyViewer has a ViewModel assigned to it. This is how it looks like in MyViewer:
<DataGrid Grid.Row="1" ItemsSource="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}, Path=Schedules}" AutoGenerateColumns="False" >
So far this is not working, when MyViewer is loaded Schedules property is null. But even if it works, I would want the Schedules to be passed to the ViewModel not code behind. One idea is to populate the Dictionary with ViewModels of MyViewer, but I do not wish to do this, I only want the Main control to know about details of MyViewer. So any clean idea to solve this?
EDIT:
The proposition above does work after changing to ObservableDictionary, but the question remains, how to have the Schedules in the ViewModel
If MyViewer has it's own ViewModel, you should not rather do hacks like this:
ItemsSource="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=UserControl}, Path=Schedules}"
This way MyViewer is tightly coupled to some other control.
Im my opinion, in this case, MyViewer's ViewModel should have it's own property Schedules. How could you keep TabControl ViewModel's Schedules and MyViewer ViewModel's Schedules in sync? It depends on your system, but you could try with this ideas:
Sending ViewModel level messages like in MVVM Light, when adding or removing items. Example in this blog post
Try to implement some kind of store like in NgXs or NgRx in Angular
Maybe you don't need to keep Schedules in sync - depends on your system? :)
I would like to know if there's a way to implement a responsive Master/Detail page using only one. What I want is something exactly like the Project here:
https://github.com/Microsoft/Windows-universal-samples/tree/master/Samples/XamlMasterDetail
Except for the detail that instead of using two pages and navigating from one to another I would only use one page.
Is there a way to do it? If so, could you link me a working example?
Except for the detail that instead of using two pages and navigating from one to another I would only use one page.
After going through the project, I found it implemented a responsive master/detail experience based on the size of the screen. When the app view is sufficiently wide, the master list and detail view should appear side by side in the same app page. However, on smaller screen sizes, the two pieces of UI should appear on different pages, allowing the user to navigate between them. From my point of view, I think this is a good solution for implementing a responsive master/detail experience.
Is there a way to do it? If so, could you link me a working example?
The project already shows how to implement responsive Master/Detail in UWP using only one page, but it implements more and that makes it a little complex to understand. So I make a simple example which directly shows how to implement responsive Master/Detail in UWP using only one page.
Following is the main steps:
First, create a ListView to show master information in xaml page:
<!--Master VIEW-->
<ListView x:Name="ItemListView" Margin="0,0,0,8">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel HorizontalAlignment="Left" Margin="10,8,0,0">
<TextBlock Text="{Binding Title}" FontSize="25" Width="400" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Second, specify the details view that shows the details item related to the selection on the master list in the same xaml page:
<!--DETAILS VIEW-->
<StackPanel Grid.Column="1" x:Name="ContentPanelDetail" Margin="10,0,0,0" DataContext="{Binding SelectedItem, ElementName=ItemListView}">
<TextBlock Text="{Binding Title}" MaxHeight="80" FontSize="30" HorizontalAlignment="Left" Margin="0" />
<TextBlock x:Name="DetailTextBlock" FontSize="35" Text="{Binding Content}" HorizontalAlignment="Left" Margin="0,18,40,0" Width="500" Height="Auto" TextWrapping="Wrap" />
</StackPanel>
Then, set the ItemsSource for the ListView in code behind:
public MainPage()
{
this.InitializeComponent();
//set the ItemsSource for the ListView
ItemDetails messageData = new ItemDetails();
ItemListView.ItemsSource = messageData.Collection;
ItemListView.SelectedIndex = 0;
}
Last but not least, put Master View and Details View into a SplitView and use VisualStateManager to make it more responsive.
Here is the simple example and the output for your reference.
To implement Master/Detail pattern on your page, you don't have to do it yourself. Instead you can use MasterDetailsView control from UWP Community Toolkit, it does a lot work for you + it is well documented.
Note: For details section of the control, do not set background to null (NoSelectionContent will be visible).
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'm starting a new project in WPF and am now looking into using Prism. For now I'm simply trying to set up the navigation of the application using Prism. Unfortunately, my lack of experience with the framework makes it a bit difficult to get started.
To be more precise about my first challenge I have an application with a "navigation/menu" region and a "main" region.
In "navigation/menu" region, I have several checkboxes, in this case we have four of them, which represents a sequential navigation. I.E. we've selected View 2 and View 4.
So, when the user click Start, in "main" region must appear each view selected in that order. Check the below image, View 2 is first. Then when the user press next, must show View 4.
I mean on a more structural level..
if I could only get through the first steps..
Prism support TabControl Region Adapter, navigation can be done using standard requestNavigation method.
You need add all your tab content using Region.Add method to the region in your module's init phase.
view:
<TabControl prism:RegionManger.RegionName="tabRegion" />
C# code:
IRegionManager manager;
manager.Regions["tabRegion"].Views.Add(Container.Resolve(typeof(YourViewType)));
In your viewModel, you should write you navigation command:
public void NextView() {
regionManager.RequestNavigation("tabRegion", new Uri("YourViewType", UriKind.Relative));
}
bind to your "next" button:
<Button Command="{Binding NextViewCommand}" />
If you want to control whether user can navigate to next page, you can implement INavigationAware interface.
If you don't want lost data between navigation, you can make your view model has ContainerMangedLifeCycle or implement IsNavigationTarget method to return true.
Sorry for untested code sample, but you should get the point.
Create a class named ViewVM with a property IsSelected. Must implement INotifyPropertyChanged.
Add an ObservableCollection<View> named Views to your datacontext. Populate it with new instances of ViewVM.
Put an ItemsControl in your Window, with ItemsSource set to Views. The DataTemplate for the ItemsControl items should contain a CheckBox (with IsChecked bound to IsSelected) and a Label.
Add a TabControl to your Window, with ItemSource set to Views. Add a Style for TabItem such that TabItems are only visible if IsSelected is true.
Following the above steps will give you a window containing a list of views with checkboxes, as you requested, and a TabControl displaying only the selected views. Below is the XAML (I have tested this):
<StackPanel>
<ItemsControl ItemsSource="{Binding Path=Views}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Path=IsSelected}"></CheckBox>
<TextBlock Text="{Binding Path=Title}"></TextBlock>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<TabControl ItemsSource="{Binding Path=Views}">
<TabControl.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Path=Title}"></TextBlock>
</Grid>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.Resources>
<Style TargetType="TabItem">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsSelected}" Value="False">
<Setter Property="Visibility" Value="Collapsed"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</TabControl.Resources>
</TabControl>
</StackPanel>
This addresses the structural/design aspect and should give you a good start to creating your solution - you'll also need to create a custom control to use instead of the TabControl. Instead of having tabs, your custom control should contain Next and Previous buttons to navigate between views.
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.