Hardcode or abstract views? - c#

I'm developing a WPF app using MVVM where I show error messages/special dialogs via a lightbox styled pop-up. These sub-Views are User Controls displayed by a ContentControl in the main View.
Up until now, each sub-View has been hardcoded to perform a single function (say, display error or ask the user to backup first). But as the app progresses, I'm seeing the the same design pattern from most of these controls:
Icon image in top left corner
Heading text next to icon
Message text in the middle
2 buttons in the bottom right corner
With MVVM I should be able to abstract this pattern and reuse this control for displaying errors, asking the user to back up, anything really, just by binding. I should even be able to bind the names of the buttons or even hide 1 if it isn't used...stuff like that.
But should I? Is there a performance benefit/hit from doing it like this? Seems like this would fall under DRY when there's 8 sub-views all with the same grid pattern.

Dry is not about performance.
It's about saving you time writing code and in the maintenance phase.
Whilst it would be more elegant to make one generic re-usable window this probably comes at some sort of cost.
Does the work cost you more than the benefit you get? The decision whether to rationalise into one probably-more-complicated view or not should be based on a sort of cost benefit analysis.
Factors you should consider:
How long does it take to make each view?
How complicated is the functionality in each?
How much effort is necessary to make a generic?
How many exceptional cases are there and how much would they complicate making this generic?
Would making this generic obscure functionality and to what extent is it going to make maintenance more expensive?
How likely is it you'll have to change the look of these things?
If you're highly unlikely to change the look, there are a few edge cases make a generic view complicated and injecting your functionality has complications then just copying and pasting markup into each view makes some sense.
Edit:
Remember that styling is re-usable.
Here's a concrete bit of markup to consider.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="*"/>
<RowDefinition Height="40"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal">
<Path Data="{Binding IconGeometry}"
Stretch="Fill"
Fill="Black"
Height="28"
Width="28"/>
<TextBlock Text="{Binding Heading}"/>
</StackPanel>
<TextBlock Grid.Row="1"
Text="{Binding Message}"/>
<ItemsControl
Grid.Row="2"
ItemsSource="{Binding NamedCommandCollection}"
HorizontalAlignment="Right">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding ButtonText}" Command="{Binding ButtonCommand}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
You present a viewmodel to this.
This viewmodel implements inotifypropertychanged and provides a string property for the heading, message etc.
The less-obvious things here are a path with a geometry rather than image. This depends on what your iconography will look like exactly but the simple one colour shape is very common now.
You can define geometries in a resource dictionary, grab the appropriate one out of there and supply it as a property. Merged resource dictionaries go in application.current.resources which is pretty much an in memory dictionary of objects keyed by a string of your x:key.
The buttons are produced by an itemscontrol which templates out it's items into a horizontal line of buttons.
Build a viewmodel representing a button.
string property for name and a relaycommand or delegatecommand for the ButtonCommand.
Let's call that a ButtonVM.
Add a ButtonVM to an observablecollection property NamedCommandCollection and you get a button appears. Add one, two, three. However many you like.
You could make the ButtonVM just take a relaycommand you build and supply or have one itself and you inject an action. You can capture variables as you build an action dynamically.
Command also has canexecute. You can use that to refine when a button can be clicked or not. EG I have a property IsBusy in a base viewmodel which I use to flag whether any command is "running" to obviate that very fast double click breaking everything.
Here it is:
public class BaseViewModel : INotifyPropertyChanged
{
private bool isBusy;
[IgnoreDataMember]
public bool IsBusy
{
get => isBusy;
set => ToVal(ref isBusy, value, nameof(IsBusy));
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public void ToObj<T>(ref T backer, T value, [CallerMemberName] string propertyName = null)
{
backer = value;
this.RaisePropertyChanged(propertyName);
}
public void ToVal<T>(ref T backer, T value, [CallerMemberName] string propertyName = null)
{
if (Equals(backer, value))
{
return;
}
backer = value;
this.RaisePropertyChanged(propertyName);
return;
}
}
Icommand has a canexecute bool and will disable a control a command is bound to if it's false. However, this relies on commandmanager deciding to requery canexecute and disable that control. There are circumstances when this won't happen fast enough. Hence it's best to use the bool to guard the code in a command.
A fairly random example out some real code:
private RelayCommand newMapCommand;
public RelayCommand NewMapCommand
{
get
{
return newMapCommand
?? (newMapCommand = new RelayCommand(
() =>
{
if (IsBusy)
{
return;
}
ResetMap();
IsBusy = false;
},
( ) => !IsBusy
));
}
}
Relaycommand is in mvvmlight. Since I work in net core nowadays and there's a dependency in commandwpf on net old, I grabbed the source for the bits I want of mvvmlight. I retain the namespaces since Laurent will probably eventually address this or net 5 may obviate the issue.
A usercontrol can itself contain a usercontrol. If you wanted flexibility then it could have a contentcontrol and template out what is bound to it's content.
This is used for viewmodel first, a common way to switch out content for navigation etc. I wrote an example to explain the evils of pages :^)
https://social.technet.microsoft.com/wiki/contents/articles/52485.wpf-tips-and-tricks-using-contentcontrol-instead-of-frame-and-page-for-navigation.aspx

Related

Switch DetailsTemplate in ListDetailsView between view and edit mode

I do have a ListDetailsView showing some data (lets say Company as a simple example here). Normally the details of a Company are shown as readonly. However, via the ListDetailsView.DetailsCommandBar it is possible to edit a Company (and also add a new Company). A clear separation between view and edit mode seems to be a good choice for the UI. I'm using a UserControl to show details of a Company.
So here are my questions:
Where should the differentiation between view- and edit-mode happen? I thought it is a good idea to have a CompanyDetailsControl and a CompanyDetailsEditControl and select between the two (both using the same CompanyDetailsViewModel). There are other solutions as well, for example, the CompanyDetailsControl could handle the edit- and view-mode internally.
Assuming that it is a good idea to switch between two UserControl, how can that be realized with the ListDetailsView.DetailsTemplate? I though it would be easy to use a DataTemplateSelector here, but that is only available for the ItemTemplate.
Not sure what code to provide to clarify my questions. So in case you need any code to better understand my question please leave a comment.
Note: I have never worked with UWP app, only applying MVVM pattern from WPF.
Straight line where the split should happen is not drawn. It often depends on the developer himself, which framework is used and more.
Me personally would go in way where UI handles UIs' things and ViewModel handles data only. That means the view is responsible for showing you the controls you are expecting to see/control the application. And when the view learns that property was changed, it should update how it looks.
Since the point we know the app will have edit & readonly modes, we should prepare all necessary UI components (UserControls, Pages, ...) to handle both those states. They would be binded to ViewModel that have base in BaseViewModel that already have this edit variable inside. So that each UI component know it can work with that.
Base view model:
abstract class BaseViewModel : INotifyPropertyChanged
{
private string mIsInEditMode;
public string IsInEditMode
{
get { return mIsInEditMode; }
set
{
if(mIsInEditMode == value) return;
mIsInEditMode = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(IsInEditMode));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
All "normal" ViewModels inherit from it:
class CompanyViewModel : BaseViewModel
{ /* props and logic of company */ }
UI component (UserControl) would have either trigger (<Style.Triggers/>) or binded properties Visibility and IsEnabled to the BaseViewModel. Those bindings would handle this logic of showing/hiding and you have potential to control whole layouts, hide controls etc.
<UserControl d:DataContext="{x:Bind local:CompanyViewModel}">
<UserControl.Resources>
<local:BoolInverterConverter x:Key="BoolInvert"/>
</UserControl.Resources>
<Grid>
<Grid IsVisible="{Binding IsInEditMode}" IsEnabled="{Binding IsInEditMode}">
<!-- Controls for edit mode -->
</Grid>
<Grid IsVisible="{Binding IsInEditMode, Converter={StaticResource BoolInvert}}"
IsEnabled="{Binding IsInEditMode, Converter={StaticResource BoolInvert}}">
<!-- Controls for readonly mode -->
</Grid>
</Grid>
</UserControl>
Note: I've used property IsVisible, You would actually use Visibility with some custom converter.

How to create a grid with another one overlapping it and show certain controls of the first grid in the second [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 2 years ago.
Improve this question
I'm creating multiple grids with textboxes and labels.
There are grids that have some textboxes in common and others ones are unique to each grid.
I'm using the visibility property to show and collapse each grid when I need it.
The question is, is there a way of making visible a textbox from a collapsed grid into a different visible grid while the grids are overlapping?
May be there is a better control to do this?
Here is a summarized example of what I want to do:
XAML
<ToolBarTray>
<ToolBar>
<Button Name="showgrid1" Click="showgrid1_Click"/>
<Button Name="showgrid2" Click="showgrid2_Click"/>
</ToolBar>
</ToolBarTray>
<Grid Name="grid1" Visibility="Collapsed">
<TextBox Name="Common"/>
<TextBox Name="UniqueTogrid1"/>
</Grid>
<Grid Name="grid2" Visibility="Collapsed">
<TextBox Name="UniqueTogrid2"/>
</Grid>
Code behind C#:
private void showgrid1_Click(object sender, RoutedEventArgs e)
{
grid1.Visibility = Visibility.Visible;
Common.Visibility = Visibility.Visible;
UniqueTogrid1.Visibility = Visibility.Visible;
grid2.Visibility = Visibility.Collapsed;
}
private void showgrid2_Click(object sender, RoutedEventArgs e)
{
grid1.Visibility = Visibility.Collapsed;
grid2.Visibility = Visibility.Visible;
UniqueTogrid2.Visibility = Visibility.Visible;
Common.Visibility = Visibility.Visible; //I want to show this textbox without declaring it in grid2 in XAML, while the grids are overlaping.
}
This code won't show the Common textbox in grid2.
Even with the edit, the question isn't entirely clear. In particular, your code example doesn't provide any actual values for the UI elements being displayed, rendering any change in visibility moot. Who cares what's visible when, if there's no data?
That said, from your use of the Click event as a way to respond to user input, I suspect that you similarly have some code somewhere that is explicitly setting the Text property for the named UI elements you're dealing with. This has led you to a desire to reuse the <TextBox Name="Common"/> element, so as to not duplicate code.
If that inference is correct, or even close to being correct, then…the motivation is honorable, but you've painted yourself into a corner through your improper use of the WPF API. Specifically, you should view the UI elements as throw-away objects, and keep all the interesting bits of your program in non-UI objects called "view models". See "MVVM" as a programming paradigm.
By "throw-away", I mean these objects are created as needed by the framework to serve the purposes of the current state of the UI. They should not take any more significant role than that. When I look at the code example you posted, there are at least a couple of major warning signs in the code: the elements have names, and there is code-behind that is manipulating their visual state.
Both of these characteristics are almost never needed in well-written WPF code. The XAML can very often completely describe not just the appearance of the UI but how it changes visual state based on the operation of the program.
Okay, so with that exposition out of the way, how to implement your code so that it better-suits the WPF API, while at the same time has minimal repetition?
I can see at least a couple of ways immediately. One is to preserve substantially the arrangement of the XAML as you've got it now, but move the important elements into a proper view model data structure. Another is to use data templates and multiple view model data structures to automatically update the UI according to what data is active at the moment. I'll show both approaches here.
Approach #1:
The first step is to create the view model. IMHO, a WPF program will almost always start with the view model, because the XAML (the user interface) exists to serve the view model (the program data), rather than the other way around. The view model should ideally not have any dependencies on the UI framework. It represents the state of your program independent of things specific to the framework, though it will often still have state that represents conditional aspects of the UI itself.
In some cases, you'll find that you choose to use the view model as an adapter between an even more-rigorous model data structure; this adds a new layer to the program, allowing for the model data structure to be entirely independent of UI concerns entirely. I didn't bother with that for this example.
class ViewModel1 : NotifyPropertyChangedBase
{
private string _commonText = "default common text view model 1";
public string CommonText
{
get => _commonText;
set => _UpdateField(ref _commonText, value);
}
private string _uniqueText1 = "default unique text #1";
public string UniqueText1
{
get => _uniqueText1;
set => _UpdateField(ref _uniqueText1, value);
}
private string _uniqueText2 = "default unique text #2";
public string UniqueText2
{
get => _uniqueText2;
set => _UpdateField(ref _uniqueText2, value);
}
private int _gridToShow;
public int GridToShow
{
get => _gridToShow;
set => _UpdateField(ref _gridToShow, value);
}
public ICommand SetGridToShowCommand { get; }
public ViewModel1()
{
SetGridToShowCommand = new SetGridToShow(this);
}
private class SetGridToShow : ICommand
{
private readonly ViewModel1 _owner;
public SetGridToShow(ViewModel1 owner)
{
_owner = owner;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter) => true;
public void Execute(object parameter)
{
if (parameter is int index ||
(parameter is string text && int.TryParse(text, out index)))
{
_owner.GridToShow = index;
}
}
}
}
This class has a few features typical of WPF view models:
It inherits a base class that does the actual work of implementing INotifyPropertyChanged.
It has public properties that represent the current state of the program, and which will be used in bindings declared in the XAML, either to display a particular value or to control some particular state of the UI.
It has public properties (well, one in this case) for commands to react to user input. In this particular example, the implementation of the single command is a standalone nested class, but in a real-world program this would typically be generalized as well, using helper classes that do things like handling type conversion for command parameters and accepting delegates for the actual implementation of a command.
In this example, the view model includes three string properties, one that represents the shared value between the two UI states, and then two more, each of which being the "unique" value for each state, an int property that represents the current UI state, and an ICommand property that handles the user input.
With that view model declared, now we can look at the XAML:
<DockPanel>
<DockPanel.DataContext>
<l:ViewModel1/>
</DockPanel.DataContext>
<ToolBarTray DockPanel.Dock="Top">
<ToolBar>
<Button Content="Show Grid 1" Command="{Binding SetGridToShowCommand}" CommandParameter="1"/>
<Button Content="Show Grid 2" Command="{Binding SetGridToShowCommand}" CommandParameter="2"/>
</ToolBar>
</ToolBarTray>
<Grid>
<StackPanel>
<StackPanel.Style>
<Style TargetType="StackPanel">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding GridToShow}" Value="1">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<TextBox Text="{Binding CommonText}"/>
<TextBox Text="{Binding UniqueText1}"/>
</StackPanel>
<StackPanel>
<StackPanel.Style>
<Style TargetType="StackPanel">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding GridToShow}" Value="2">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<TextBox Text="{Binding CommonText}"/>
<TextBox Text="{Binding UniqueText2}"/>
</StackPanel>
</Grid>
</DockPanel>
The important parts in the above, relative to your question, are:
Most important, the CommonText property is bound to two different TextBox elements. I.e. the XAML element is not shared (which would have been the literal answer to your question), but rather the underlying view model property is shared. This allows the UI to interact with the user in whatever manner is appropriate for the given UI state, while only have a single state in the view model that represents the user's input.
The view model object is set as the data context for this part of the visual tree, via the DockPanel.DataContext element binding.
The user input isn't implemented with handlers for the Click event, but rather via the ICommand that updates the view model state according to the input.
The UI state itself responds to the changes in the view model via DataTrigger elements provided in the Style elements set for each "grid" (I used a StackPanel instead of a Grid in this example, because it was more convenient, but the same general ideas apply regardless.)
Approach #2:
That example alone I think sufficiently addresses the scenario you describe. However, WPF also can display an entirely different configuration of UI elements for a given data context object, through the mechanism of data templates. If we apply that idea to your question, we can:
Establish a couple more view model objects to represent the "unique" values in the program.
Declare a template for each of the view model objects.
Instead of using DataTrigger to change the visual state of the UI, let WPF automatically update the state via the templates, by simply updating the current view model being displayed.
In this scheme, here are the view model objects I came up with…
The main one:
class ViewModel2 : NotifyPropertyChangedBase
{
private readonly ViewModel2A _viewModel2A = new ViewModel2A();
private readonly ViewModel2B _viewModel2B = new ViewModel2B();
public string CommonText => "common text view model 2";
private object _gridViewModel;
public object GridViewModel
{
get => _gridViewModel;
set => _UpdateField(ref _gridViewModel, value);
}
public ICommand SetGridToShowCommand { get; }
public ViewModel2()
{
SetGridToShowCommand = new SetGridToShow(this);
}
private class SetGridToShow : ICommand
{
private readonly ViewModel2 _owner;
public SetGridToShow(ViewModel2 owner)
{
_owner = owner;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter) => true;
public void Execute(object parameter)
{
if (parameter is int index ||
(parameter is string text && int.TryParse(text, out index)))
{
_owner.SetGridToShowIndex(index);
}
}
}
private void SetGridToShowIndex(int index)
{
GridViewModel = index == 1 ? (object)_viewModel2A : _viewModel2B;
}
}
And the two "unique" ones:
class ViewModel2A
{
public string UniqueText1 => "unique text grid #1";
}
class ViewModel2B
{
public string UniqueText2 => "unique text grid #2";
}
I skipped the INotifyPropertyChanged for the purposes of this example, and just made these view models with read-only/display-only properties.
Note that in the main view model, all it does when the user input occurs, is to set the current "grid" view model to the appropriate "unique" view model object.
With that in place, we can write the XAML:
<DockPanel Grid.Column="1">
<DockPanel.DataContext>
<l:ViewModel2/>
</DockPanel.DataContext>
<DockPanel.Resources>
<DataTemplate DataType="{x:Type l:ViewModel2A}">
<StackPanel>
<!-- OneWay binding for illustration purposes (view model property is read-only) -->
<!-- RelativeSource allows for referencing properties from other than the current data context, such as the common text property -->
<TextBox Text="{Binding DataContext.CommonText, Mode=OneWay, RelativeSource={RelativeSource AncestorType=DockPanel}}"/>
<TextBox Text="{Binding UniqueText1, Mode=OneWay}"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type l:ViewModel2B}">
<StackPanel>
<TextBox Text="{Binding DataContext.CommonText, Mode=OneWay, RelativeSource={RelativeSource AncestorType=DockPanel}}"/>
<TextBox Text="{Binding UniqueText2, Mode=OneWay}"/>
</StackPanel>
</DataTemplate>
</DockPanel.Resources>
<ToolBarTray DockPanel.Dock="Top">
<ToolBar>
<Button Content="Show Grid 1" Command="{Binding SetGridToShowCommand}" CommandParameter="1"/>
<Button Content="Show Grid 2" Command="{Binding SetGridToShowCommand}" CommandParameter="2"/>
</ToolBar>
</ToolBarTray>
<Grid>
<ContentControl Content="{Binding GridViewModel}"/>
</Grid>
</DockPanel>
Here, rather than setting styles with triggers, there are two different templates declared in the resource dictionary of the parent DockPanel element, one for each "unique" view model type. Then in the Grid control, the content is simply bound to the current "unique" view model object. WPF will select the correct template according to the type of that current "unique" view model object.
One slightly complicated thing I did in the XAML above was to put the CommonText property in the main view model, making it actually common to both view states. Then the templates both refer to it by using the RelativeSource mode for the binding. It would also have been possible instead to have the data templates only provide UI elements for the "unique" properties, and have the parent UI element handle display of the CommonText property. That would arguably be a little cleaner and less repetitive, but it would also have been a significant enough departure from the code you originally posted that I decided not to cross that bridge. :)
Finally, all of the above relies on that base class I mentioned earlier to implement INotifyPropertyChanged. There are various ways to implement that, but to complete the above example, here's the implementation I used for the code above:
class NotifyPropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void _UpdateField<T>(ref T field, T newValue,
Action<T> onChangedCallback = null,
[CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, newValue))
{
return;
}
T oldValue = field;
field = newValue;
onChangedCallback?.Invoke(oldValue);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

Is there a way to set DataTemplate for a control in an user control (a composite one) in XAML

I have created a composite user control containing a Toolbar and a Datagrid, and expose them as public properties. Is there a way to add new button to Toolbar and set a DataTemplate for Datagrid in XAML, instead of implementing them in the code-behind file if I use this user control in another Window or user control?
I found a similar link here, but has no idea how to do it. Please help.
Here is the Xaml:
<UserControl x:Class="CRUDDataGrid1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" >
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ToolBarTray Grid.Row="0" >
<ToolBar x:Name="tb">
<Button x:Name="Add" Content="Add">
</Button>
</ToolBar>
</ToolBarTray>
<DataGrid Grid.Row="1" x:Name="dg">
</DataGrid>
</Grid>
</UserControl>
And here is the code-behind:
public partial class CRUDDataGrid1 : UserControl
{
public ToolBar ToolBar { get; set; }
public DataGrid DataGrid { get; set; }
public ObservableCollection<DataGridColumn> Columns { get; private set; } //edited
public CRUDDataGrid1()
{
InitializeComponent();
ToolBar = tb;
DataGrid = dg;
Columns = dg.Columns; //edited
}
}
And I want to use this user control in another user control like this:
<UserControl x:Class="UserControl1" ...>
<Grid>
<local:CRUDDataGrid1>
<local:CRUDDataGrid1.ToolBar>
<Button x:Name="Delete" Content="Delete">
</Button>
</local:CRUDDataGrid1.ToolBar>
<local:CRUDDataGrid1.DataGrid ItemsSource="{Binding Customers}">
<local:CRUDDataGrid1.Columns>
<DataGridTextColumn Header="First Name" Binding="{Binding XPath=#FirstName}" />
<DataGridTextColumn Header="Last Name" Binding="{Binding XPath=#LastName}" />
<local:CRUDDataGrid1.Columns>
</local:CRUDDataGrid1.DataGrid>
</local:CRUDDataGrid1>
</Grid>
</UserControl>
1 Foreword
Having a child control which owns a ToolBar and wanting a parent of that child control to add toolbar items to the ToolBar owned by the child is a tell-tale sign of bad
design. The primary and most important advice for you is to rethink your software design to avoid this kind of shared/split initialization.
In almost any scenario, you want the toolbar owned by the top-most control such as the main window, or a document window (in case your application has MDI or floating windows).
The toolbar items would be gathered from the respective controls housed within that window; for example, copy/paste/etc. actions from the document editor control, actions for creating or loading a new document from somewhere else, etc.
Side note: Often, such a design happens because novice WPF programmers want to realize button actions in the old-fashioned way of using Click event handlers. Such Click-event
handlers create code dependencies, and as long as they can be contained within just one (custom) control everything is fine. However, as soon as this is not feasible any more
(for example when an action should appear as a toolbar button or the same action should be triggered through a menu), trying to stick with Click event handlers will lead to convoluted code even for simple UIs and can cause severe headache...
The mechanism in WPF to avoid those pesky Click event handlers are Commands, or more specifically RoutedCommands. To be fair, it has to be noted that RoutedCommands have their own
share of challenges. However, many fine folks wrote many interesting and important things about using WPF's RoutedCommands and how to expand beyond their functionality, so the
only sane advice i can give here is to use the powers of Google if you want/need to know more.
2 Answering the question, but not solving the underlying design issue
To create a ToolBar which has collections of toolbar items defined at different places, while using multiple toolbar bands in the same ToolBarTray is not desired, the toolbar item collections need to be merged into a single list at some point. This can either be done somehow in code-behind, or it can be done in XAML with the help of a custom IMultiValueConverter.
The custom IMultiValueConverter - let's call it MergeCollectionsConverter - will be
agnostic to any data type. It just takes a number of IEnumerables and adds all their elements to the result list. It even accepts objects which are not IEnumerable, those objects themselves will be added to the result list.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Windows.Data;
using System.Windows.Documents;
namespace MyStuff
{
public class MergeCollectionsConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values == null) return null;
List<object> combinedList = new List<object>();
foreach (object o in values)
{
if (o is IEnumerable)
combinedList.AddRange( ((IEnumerable) o).Cast<object>() );
else
combinedList.Add(o);
}
return combinedList;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
I assume furthermore that the ToolBar inside of CRUDDataGrid1 should be fed from two toolbar item collections. The first collection with the default toolbar items is defined within CRUDDataGrid1. The second collection should allow other controls to append additional toolbar items after the default items; this collection therefore has to be publicly accessible.
Based on your example code from the question, your CRUDDataGrid1 class could look like the following (just considering the toolbar, it does not represent the complete class by any means):
CRUDDataGrid1.cs:
public partial class CRUDDataGrid1 : UserControl, INotifyPropertyChanged
{
public ObservableCollection<object> AdditionalToolbarItems { get { return _additionalToolbarItems; } }
private readonly ObservableCollection<object> _additionalToolbarItems = new ObservableCollection<object>();
public event PropertyChangedEventHandler PropertyChanged;
public UserControl1()
{
InitializeComponent();
_additionalToolbarItems.CollectionChanged +=
(sender, eventArgs) =>
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs("AdditionalToolbarItems"));
};
...other constructor code...
}
}
CRUDDataGrid1.xaml:
<DockPanel>
<ToolBarTray DockPanel.Dock="Top">
<ToolBar>
<ToolBar.Resources>
<DataTemplate DataType="{x:Type My:UseCommand}">
<Button
Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}"
Command="{Binding Command}"
CommandTarget="{Binding Target}"
CommandParameter="{Binding Parameter}"
Content="{Binding Command.Text}"
/>
</DataTemplate>
<My:MergeCollectionsConverter x:Key="convToolbarItems" />
<x:Array x:Key="defaultToolbarItems" Type="{x:Type sys:Object}">
<My:UseCommand Command="ApplicationCommands.New" />
<My:UseCommand Command="ApplicationCommands.Cut" />
<My:UseCommand Command="ApplicationCommands.Paste" />
</x:Array>
</ToolBar.Resources>
<ToolBar.ItemsSource>
<MultiBinding Converter="{StaticResource convToolbarItems}">
<Binding Source="{StaticResource defaultToolbarItems}" />
<Binding Path="AdditionalToolbarItems" ElementName="crudDataGrid1" />
</MultiBinding>
</ToolBar.ItemsSource>
</ToolBar>
</ToolBarTray>
<DataGrid x:Name="dg" />
</DockPanel>
The first collection is a 'static resource' in the ToolBar's resource directory,
identified by the resource key "defaultToolbarItems". The second is the collection provided by CRUDDataGrid1's property AdditionalToolbarItems. Using a <MultiBinding>
with the aforementioned converter, the merged list is bound to the ToolBar's ItemsSource.
Looking at the C# source code of the AdditionalToolbarItems property, you will notice the INotifyPropertyChanged implementation and the handler for the CollectionChanged
event. Why is that? Remember, that AdditionalToolbarItems is a read-only property. At the time the CRUDDataGrid1 control has been fully constructed, the data binding has been
set and AdditionalToolbarItems been processed by the multi binding. And it would never be
processed again, since the property itself will never change its value (it will
always refer to the same ObservableCollection). To make the <MultiBinding> re-evaluate the bound properties whenever the content of the AdditionalToolbarItems collection has
changed, the code needs to listen for CollectionChanged events and fire an
PropertyChanged event whenever the content of AdditionalToolbarItems has changed, which in turn will cause the <MultiBinding> to re-evaluate the bound properties.
You will also note the usage of <My:UseCommand> elements instead of using <Button>. Well, you could use <Button>, and it would work as well. Until your application wants to use multiple ToolBars at once sharing the same default buttons - in which case you have a problem: A button is a control and thus has one parent UI element. You cannot share
a button control amongst several toolbars, because a control can only be owned as a child by one parent UI element. Thus, RoutedCommands are used instead of button controls
(another, equally important reason will become obvious if you read the 'real' solution in section 3 below). Still, technically nothing would stop you from declaring <Button>
elements -- you could even mix <My:UseCommand> with <Button> (and other elements, as long as those can be rendered in the toolbar).
UseCommand is a pretty small and simple class which allows you to tell which command to use (plus optional CommandTarget and CommandParameter, if required):
namespace MyStuff
{
public class UseCommand
{
public System.Windows.Input.ICommand Command { get; set; }
public System.Windows.IInputElement Target { get; set; }
public object Parameter { get; set; }
}
}
The ToolBar will need a DataTemplate to properly display the command and its parameters stored inside UseCommand. You can see this DataTemplate as part of the ToolBar
resource dictionary in the XAML code above.
With these things in place, using CRUDDataGrid1 in UserControl1 and adding additional toolbar items could look like this:
<UserControl x:Class="UserControl1" ...>
<Grid>
<local:CRUDDataGrid1>
<local:CRUDDataGrid1.AdditionalToolbarItems x:Name="cdg">
<My:UseCommand Command="ApplicationCommands.Close" CommandTarget="{Binding ElementName=cdg}" />
<My:UseCommand Command="ApplicationCommands.New" CommandTarget="{Binding ElementName=cdg}" />
</local:CRUDDataGrid1.AdditionalToolbarItems>
...
</local:CRUDDataGrid1.DataGrid>
</Grid>
</UserControl>
For my example code, i used commands provided by System.Windows.Input.ApplicationCommands. You can ofcourse roll your own commands (as we will see below). Also note the demonstrated usage of the CommandTarget property. Whether using this property is necessary requires some understanding of how RoutedCommands work and mostly depends on where which element in the UI's visual/logical tree has established handlers for that particular command.
3 Using RoutedCommands to solve the design issue and the question
Having read section 2, you should already gotten the idea that RoutedCommands will help you to separate the provision of user-invokable actions by whatever component from the actual UI representation, and that this can help you avoiding the shenanigans about the somewhat convoluted composition of the ToolBar from different sources. Because, all that CRUDDataGrid1 essentially needs to provide for your GUI are the commands for a toolbar (or a menu, or any other command invokers for that matter).
From what i can glance from your source code, CRUDDataGrid1 is responsible for executing the "Add" action, whereas UserControl1 is responsible the "Delete" action.
Both actions should appear in the same toolbar.
Let's look at the "Add" action of CRUDDataGrid1. First and foremost, to make this action invokable through a RoutedCommand, an appropriate RoutedCommand object needs to be provided, obviously. You might choose one of the RoutedCommands provided by .NET (as declared in ApplicationCommands, ComponentCommand and NavigationCommand).
However, this is not always a good idea. Common commands such as ApplicationCommands.Copy can be executed by pretty much any control which supports clipboard operations, and knowing which actual control will handle the invocation of such a command requires knowing about how RoutedCommands are routed through the visual tree and how the logcial focus affects this routing. Thus, sometimes it is
easier to define your own RoutedCommand as a public static property - which we will do here for the "Add" action:
public partial class CRUDDataGrid1 : UserControl
{
public static readonly RoutedCommand AddCommand = new RoutedCommand("CRUDDataGridCommand.Add", typeof(CRUDDataGrid1));
public UserControl1()
{
InitializeComponent();
CommandBindings.Add(
new CommandBinding(
AddCommand,
OnExecutedAddCommand,
CanExecuteAddCommand
)
);
...other constructor code...
}
private void CanExecuteAddCommand(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = ...here your code that decides whether the "Add" command can execute
(and thus whether any button which uses this command will be enabled/disabled)
}
private void CanExecuteAddCommand(object sender, ExecutedRoutedEventArgs e)
{
...execute the "Add" action here...
}
}
Note the command-binding in the constructor as well as the respective methods handling the command. Just to avoid confusion: It is not required that the object serving as CommandTarget has to implement command-bindings. CommandTarget merely specifies the object in the visual/logical tree at which the routing starts.
While i do not show it here, the implementation regarding the DeleteCommand in UserControl is following the same pattern.
public partial class UserControl1 : UserControl
{
public static readonly RoutedCommand DeleteCommand = new RoutedCommand("UserControl1Command.Delete", typeof(UserControl1));
...same implementation approach as demonstrated for CRUDDataGrid1.AddCommand...
}
Creating the ToolBar can now happen entirely in UserControl1.xaml without worrying how the respective actions represented by the commands are executed. Note, that it is fine to use <Button> since the toolbar is entirely created in UserControl1 without a possibility that any of these buttons could be "shared" with another control. Also, note the absence of those helper classes like UseCommand and MergeCollectionsConverter which were required for the somewhat convoluted scenario in section 2 of my answer.
<UserControl x:Class="UserControl1" ...>
<DockPanel>
<ToolBarTray DockPanel.Dock="Top">
<ToolBar>
<Button Content="Add" Command="{x:Static local:CRUDDataGrid1.AddCommand}" CommandTarget="{Binding ElementName=cdg}" />
<Button Content="Delete" Command="{x:Static local:UserControl1.DeleteCommand}" />
</ToolBar>
</ToolBarTray>
<local:CRUDDataGrid1 x:Name="cdg" ItemsSource="{Binding Customers}">
<local:CRUDDataGrid1.Columns>
<DataGridTextColumn Header="First Name" Binding="{Binding XPath=#FirstName}" />
<DataGridTextColumn Header="Last Name" Binding="{Binding XPath=#LastName}" />
</local:CRUDDataGrid1.Columns>
</local:CRUDDataGrid1>
</DockPanel>
</UserControl>
CRUDataGrid1 should probably directly inherit from the DataGrid type (not being a UserControl), implementing the extended CRUD functionality as you require.
By letting CRUDataGrid1 provide only RoutedCommands for any desired user-action, you and anybody else in your team is free in the decision where in the GUI to use the RoutedCommands - in tool bars, in menus or whereever else. You can use multiple buttons using the same command - no problem there. The infrastructure behind RoutedCommands also will take care about automatically enabling/disabling such buttons depending on the result of the CanExecute method bound to a command.
In the example given here, i did let CRUDataGrid1 and UserControl1 provide the RoutedCommands. But if you have many commands and more complex software, then there is nothing speaking against having a central place for defining those commands (similar to what Microsoft did with the RoutedCommands provided by the .NET framework).

WPF Navigate through views using MVVM pattern

I'm building my first WPF using MVVM pattern. With the help of this community, I manage to create my Model, my first ViewModel and view. Now I want to add some complexity to the app designing the basic application layout interface. My idea is to have at least 2 child views and one main view and separate them on several XAML:
Main.XAML
Products.XAML
Clients.XAML
Main will have a menu and a space to load child views (Products and Clients). Now following MVVM pattern all the navigation logic between views should be write on a ViewModel. So mi idea is to have 4 ViewModels:
MainViewModel
ProductsViewModel
ClientsViewModel
NavigationViewModel
So NavigationViewModel should contain a collection of child viewmodels? and an active viewmodel is that right?
So my questions are:
1) How can I load different views (Products, Clients) on Main view using MVVM pattern?
2) How do I implement navigation viewModel?
3) How can I control the max number of open or active views?
4) How can I switch between open views?
I have been doing a lot of search and reading and couldn't find any simple working example of MVVM navigation with WPF that loads multiple views inside a main view. Many of then:
1) Use external toolkit, which I don't want to use right now.
2) Put all the code for creating all the views in a single XAML file, which doesn't seems like a good idea because I need to implement near 80 views!
I'm in the right path here? Any help, especially with some code will be appreciated.
UPDATE
So, I build a test project following #LordTakkera advices, but get stuck. This is how my solution looks like:
I create:
Two Models (Clients and Products)
One MainWindow and two wpf user controls(Clients and Products) XAML.
Three ViewModels (Clients, Products and Main ViewModel)
Then I set dataContext on each view to corresponding viewModel. After that I create MainWindow with the ContentPresenter like this and bind it to a property of the viewmodel.
MainWindow.XAML
<Window x:Class="PruevaMVVMNavNew.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="519" Width="890">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="80"/>
<RowDefinition Height="*"/>
<RowDefinition Height="20"/>
</Grid.RowDefinitions>
<Border Grid.Column="0" Grid.ColumnSpan="2" Background="AntiqueWhite" ></Border>
<Border Grid.Row="1" Grid.RowSpan="2" Background="AliceBlue"></Border>
<Border Grid.Row="1" Grid.Column="1" Background="CadetBlue"></Border>
<ContentPresenter Grid.Row="1" Grid.Column="1" x:Name="ContentArea" Content="{Binding CurrentView}"/>
<StackPanel Margin="5" Grid.Column="0" Grid.Row="1">
<Button>Clients</Button>
<Button>Products</Button>
</StackPanel>
</Grid>
And also this is viewmodel from MainWindow:
class Main_ViewModel : BaseViewModel
{
public Main_ViewModel()
{
CurrentView = new Clients();
}
private UserControl _currentView;
public UserControl CurrentView
{
get
{
return _currentView;
}
set
{
if (value != _currentView)
{
_currentView = value;
OnPropertyChanged("CurrentView");
}
}
}
}
So this load by default clients view and looks like this (which is just right!):
So I suppose I need a way to relate the buttons on the left, with a certain viemodel and then bind them with CurrentView Property of Main viewModel. How can I do that?
UPDATE2
According to #LordTakkera advice I modify my main viewModel this way:
class Main_ViewModel : BaseViewModel
{
public ICommand SwitchViewsCommand { get; private set; }
public Main_ViewModel()
{
//CurrentView = new Clients();
SwitchViewsCommand = new RelayCommand((parameter) => CurrentView = (UserControl)Activator.CreateInstance(parameter as Type));
}
private UserControl _currentView;
public UserControl CurrentView
{
get
{
return _currentView;
}
set
{
if (value != _currentView)
{
_currentView = value;
OnPropertyChanged("CurrentView");
}
}
}
}
I use RelayCommand instead of DelegateCommand but I think it works the same way. The command is executed when I hit the buttons and the type parameter string its ok but i get this error:
Translation: Value cannot be null. Parameter name: type. Suggestion use New keyword to create object instance
I don't know where to put the New keyword. I have try on CommandParameter but it wont work. Any idea? Thanks
UPDATE 3
After all the advices and help received here, and a lot of work, here is my final navigation menu and the base for my application interface.
I'm not sure you need a separate "navigation" view model, you could easily put it into the main. Either way:
To separate your "child" views, I would use a simple ContentPresenter on your "main" view:
<ContentPresenter Content="{Binding CurrentView}"/>
The easiest way to implement the backing property is to make it a UserControl, though some would argue that doing so violates MVVM (since the ViewModel is now dependent on a "View" class). You could make it an object, but you lose some type safety. Each view would be a UserControl in this case.
To switch between them, you are going to need some sort of selection control. I've done this with radio buttons before, you bind them like so:
<RadioButton Content="View 1" IsChecked="{Binding Path=CurrentView, Converter={StaticResource InstanceEqualsConverter}, ConverterParameter={x:Type views:View1}"/>
The converter is pretty simple, in "Convert" it just checks if the current control is a type of the parameter, in "ConvertBack" it returns a new instance of the parameter.
public class InstanceEqualsConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (parameter as Type).IsInstanceOfType(value);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (bool)value ? Activator.CreateInstance(parameter as Type) : Binding.DoNothing;
}
}
Binding to a combobox or other selection control would follow a similar pattern.
Of course you could also use DataTemplates (with a selector, unfortunately not something I have done before) and load them into your resources using merged dictionaries (allowing separate XAML). I personally prefer the user control route, pick which is best for you!
This approach is "one view at a time". It would be relatively easy to convert to multiple views (your UserControl becomes a collection of user controls, use .Contains in the converter etc.).
To do this with buttons, I would use commands and take advantage of the CommandParameter.
The button XAML would look like:
<Button ... Command={Binding SwitchViewsCommand} CommandParameter={x:Type local:ClientsView}/>
Then you have a delegate command (tutorial here) that runs the activator code from the converter:
public ICommand SwitchViewsCommand {get; private set;}
public MainViewModel()
{
SwitchViewsCommand = new DelegateCommand((parameter) => CurrentView = Activator.CreateInstance(parameter as Type));
}
That is off the top of my head, but should be pretty close. Let me know how it goes!
Let me know if I provide any further information!
Update:
To answer your concerns:
Yes, each time you push the button a new instance of the view is created. You could easily fix this by holding a Dictionary<Type, UserControl> that has pre-created views and index into it. For that matter, you could use a Dictonary<String, UserControl> and use simple strings as the converter parameters. The disadvantage is that your ViewModel becomes tightly coupled to the kinds of views it can present (since it has to populate said Dictionary).
The class should get disposed, as long as no one else holds a reference to it (think event handlers that it registered for).
As you point out, only one view is created at a time so you shouldn't need to worry about memory. You are, of course, calling a constructor but that isn't THAT expensive, particularly on modern computers where we tend to have plenty of CPU time to spare. As always, the answer to performance questions is "Benchmark it" because only you have access to the intended deployment targets and entire source to see what actually performs the best.
IMHO the best choose for you is to use MVVM framework (PRISM, MMVM Light, Chinch, etc) because navigation is already implemented. If you want to create your own navigation - try DataTemplate.

Improving binding performance in WPF?

I realize this question could be boiled down to "Why is my code so slow?" but I'm hoping to get more out of that. Let me explain my code.
I have a class that implements INotifyPropertyChanged in order to do binding, and that class looks similar to this:
public class Employee : INotifyPropertyChanged
{
string m_strName = "";
string m_strPicturePath = "";
public event PropertyChangedEventHandler PropertyChanged;
public string Picture
{
get { return this.m_strPicturePath; }
set { this.m_strPicturePath = value;
NotifyPropertyChanged("Picture"); }
}
public string Name
{
get { return this.m_strName; }
set { this.m_strName = value;
NotifyPropertyChanged("Name");
}
}
private void NotifyPropertyChanged(String pPropName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(pPropName));
}
}
}
In my XAML I've created a DataTemplate that binds to this object:
<DataTemplate x:Key="EmployeeTemplate">
<Border Height="45" CornerRadius="0" BorderBrush="Gray" BorderThickness="0" Background="Transparent" x:Name="bordItem">
<Grid Width="Auto">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Path=Name}" VerticalAlignment="Center" Padding="10" HorizontalAlignment="Stretch" FontWeight="Bold" FontSize="20"/>
<Image Grid.Column="1" Source="{Binding Path=Picture}"></Image>
</Grid>
</Border>
</DataTemplate>
and then put this template on a ListBox:
<ListBox x:Name="lstEmployees" ItemTemplate="{DynamicResource EmployeeTemplate}" VirtualizingStackPanel.VirtualizationMode="Recycling" VirtualizingStackPanel.IsVirtualizing="True"></ListBox>
So in code it's set as:
lstEmployees.ItemsSource = this.m_Employees;
the "m_Employees" list gets hydrated at app startup from a database, and then after that happens I set the above line of code. The ListBox is on a TabControl.
Now, my actual problem: My "m_Employees" list is returning about 500+ employees from the database, so the collection is slightly big. I get a performance hit in WPF only when the application first starts up and someone navigates to that tab with the ListBox on it. The UI freezes for about 3 seconds, but only when the app first starts up - afterwards it's fine.
Could this be because:
The code has to hit the hard drive to go find the image of each employee?
I am doing Virtualizing incorrectly?
EDIT
WPF is doing the rendering using my DataTemplate once, only when someone navigates to that TabControl, and is suddenly trying to draw 500+ employee items? If so, is there any way to "preload" the ListView in WPF?
Any other suggestions for improving the above would be apprecated. Thanks for reading and for any advice ahead of time.
-R.
Wrap m_Employees with a public
property (Employees)
Instead of setting your ItemsSource in the code like you do, set it with Binding and set IsAsync to
True.
ItemsSource="{Binding Empolyess, IsAsync=True}"
You can also assign the Binding in the code.
Hope this helps.
The perf of your query is definitely suspect. If you want it to perform better, you can do any number of lazy initialization techniques to get it to run faster.
The easiest option would be to start with an empty enumeration, and only populate it at a later time.
The obvious way to do this would be to add a "Query" or "Refresh" button, and only freeze up the app when the user clicks it.
Another simple option is to queue a background task/thread to do the refresh.
If you are more concerned about consistent perf/super-responsive UI, then you should try to do more granular queries.
I am not sure if WPF handles virtualization of the items (only pulls from the enumeration when each item comes into view), but if it does, you could do paging/yield returns to feed ItemsSource.
If WPF just grabs the whole enumeration at once, you could still do smaller lazy-eval/paging, if you can determine which items are in view. Just populate the object with "zombie" items, and when they come into view, perform the query, and update the properties on the individual item.

Categories

Resources