Should I be writing unit tests like the following?
Code:
public ObservableCollection<DXTabItem> Tabs { get; private set; }
public ICommand CustomersCommand { get; private set; }
CustomersCommand = new DelegateCommand(OpenCustomers);
private void OpenCustomers()
{
var projectService = new ProjectService(Project.FilePath);
var vm = new CustomersViewModel(projectService);
AddTab("Customers", new CustomersView(vm));
}
public void AddTab(string tabName, object content, bool allowHide = true)
{
Tabs.Add(new DXTabItem { Header = tabName, Content = content });
}
Test:
[TestMethod]
public void CustomerCommandAddsTab()
{
_vm.CustomersCommand.Execute(null);
Assert.AreEqual("Customers", _vm.Tabs[1].Header);
}
XAML:
<dx:DXTabControl ItemsSource="{Binding Tabs}" />
I am using the TDD approach, so this is working code, and it passes the tests locally, however on a server CI build it fails this test because the view (CustomersView) has something inside it that doesn't work. So I realized this test, even though its simple is actually breaking MVVM. I am writing UI code inside the ViewModel by referencing DXTabItems and even new'ing up a View.
What is the correct approach for something like this? Should I not write tests like this at all (and rely on automated testing) or should I refactor it somehow so that the ViewModel contains no UI elements, tips on how I should do that would be useful.
Clarification:
The reason for this kind of design is that each Tab contains a different View, for example the Customers Tab contains the CustomersView, yet another Tab would contain something completely different, in data and presentation. So its hard to define a mechanism that will allow for that in MVVM fashion. At least the answer is not trivial.
If DXTabItem is derived from TabItem then this is not MVVM, in MVVM you never access view elements directly in the view model. What you should be doing instead is creating a view model for your tabs (e.g. TabViewModel), change Tabs to be an ObservableCollection<TabViewModel> and bind your tab control's ItemsSource property to that to create the GUI tabs themselves.
As for your CI failing, you shouldn't ever be creating GUI elements (i.e. CustomersView) in unit tests. The only time you'd do that is during integration testing, which is a different kettle of fish. Views should only ever be loosely coupled to the view model though the mechanism of data-binding, you should be able to both run and test your entire application without creating a single view object.
UPDATE: Actually it's very easy...once you know how! :) There are a couple of different ways to achieve what you're trying to do but the two most common approaches are data templates and triggers.
With data templates you rely on the fact that your view models are supposed to represent the logic behind your GUI. If you have a Client tab and an Product tab (say) then those should have corresponding view models i.e. ClientPage and ProductPage. You may wish to create a base class for these (e.g. TabViewModel) in which case your view model collection would be a ObservableCollection<TabViewModel> as I explained above, otherwise just make it a ObservableCollection<object>. Either way you then use data templates to specify which view to create for each tab:
<DataTemplate DataType="{x:Type vm:ClientPage>
<view:ClientView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:ProductPage>
<view:ProductView />
</DataTemplate>
ListBox and other collection elements will apply these data templates automatically, alternatively you can specify ListBox.ItemTemplate explicitly and use a ContentControl where needed.
The second method is to use data triggers. If your pages are fixed then I find it helps to create an enumeration in your view model layer for reasons I'll explain in a minute:
public enum PageType : int
{
Client,
Product,
... etc ...
}
Back in your XAML you'll want to create a page for each of these, you can do that in your VM if you like although it's such an easy task I usually do it in XAML:
<ObjectDataProvider MethodName="GetValues" ObjectType="{x:Type sys:Enum}" x:Key="PageType">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="vm:PageType" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
Now you can create a TabControl and bind ItemsSource to this object and a separate tab will appear for each item in your enum:
<TabControl ItemsSource="{Binding Source={StaticResource PageType}}"
SelectedIndex="{Binding CurrentPage, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}"
IsSynchronizedWithCurrentItem="True">
CurrentPage is, of course, a property in your MainViewModel of type PageType
private PageType _CurrentPage;
public PageType CurrentPage
{
get { return _CurrentPage; }
set { _CurrentPage = value; RaisePropertyChanged(); }
}
XAML isn't smart enough to deal with enums so you'll also need the code for EnumToIntConverter which converts between the two:
public class EnumToIntConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (int)value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return Enum.ToObject(targetType, value);
}
#endregion
}
Using an enum like this might seem like a bit more work than needed but it does mean that your view model code can now set the active page at any time by doing something like `this.CurrentPage = PageType.Client'. This is especially handy in the later stages of an application where you might want have a list of products somewhere else in your application and you want to provide the user with a button (say) which opens up the product page. This provides your entire application with a lot of control over the behaviour of your tabs. Of course it also means you get notification whenever the user changes Tabs (i.e. when this.CurrentPage changes value) which can be useful for loading data on demand to improve the performance of your application...it doesn't matter if you change the order of the pages around in your enum later because your view model code is checking against an enum instead of an integer page number!
The only other thing I haven't shown is how to display the appropriate child content on each of the pages, and like I said this is done with a data trigger in your listbox item style:
<TabControl.Resources>
<Style TargetType="{x:Type TabItem}" BasedOn="{StaticResource {x:Type TabItem}}">
<Style.Triggers>
<!-- Client -->
<DataTrigger Binding="{Binding}" Value="{x:Static vm:PageType.Client}">
<Setter Property="Header" Value="Client" />
<Setter Property="Content">
<Setter.Value>
<view:ClientView DataContext="{Binding ElementName=parentTab, Path=DataContext.ClientPage"/>
</Setter.Value>
</Setter>
</DataTrigger>
<!-- Product -->
<DataTrigger Binding="{Binding}" Value="{x:Static vm:PageType.Product}">
<Setter Property="Header" Value="Product" />
<Setter Property="Content">
<Setter.Value>
<view:ProductView DataContext="{Binding ElementName=parentTab, Path=DataContext.ProductPage"/>
</Setter.Value>
</Setter>
</DataTrigger>
As you can see each DataTrigger is simply checking to see which enum its DataContext has been set to and setting it's own Header and Content accordingly.
Related
I cannot get item container from the ListBox in Backstage. Say, I have the following Backstage:
<!-- Backstage -->
<r:Ribbon.Menu>
<r:Backstage x:Name="backStage">
<r:BackstageTabControl>
<r:BackstageTabItem Header="Columns">
<Grid>
<ListBox Grid.Row="1" Grid.Column="0" x:Name="lstColumns"/>
</Grid>
</r:BackstageTabItem>
</r:BackstageTabControl>
</r:Backstage>
</r:Ribbon.Menu>
I fill it up:
public Root()
{
ContentRendered += delegate
{
var list = new List<int> { 1, 2, 3 };
foreach (var index in list)
{
lstColumns.Items.Add(index);
}
};
}
Next, I want to retrieve the item container (in this case - ListBoxItem) from the first entry of ListBox:
private void OnGetProperties(object sender, RoutedEventArgs e)
{
// Get first item container
var container = lstColumns.ItemContainerGenerator.ContainerFromIndex(0);
if (container is not null)
{
MessageBox.Show($"container = {container.GetType().FullName}");
}
else
{
MessageBox.Show("container is null");
}
}
But container is always null. But! If I open Backstage and then hide it, I see the message:
container = System.Windows.Controls.ListBoxItem.
So, I decided to add code which opens Backstage before filling it up:
backStage.IsOpen = true;
var list = new List<int> { 1, 2, 3 };
foreach (var index in list)
{
lstColumns.Items.Add(index);
}
backStage.IsOpen = false;
This works, but there's a flickering when you can barely see that Backstage is shown and hidden. This is not the perfect solution. So, how to get the item container?
P.S. Test project is here.
UPDATE (EXPLANATION)
The reason I need the item container is that I need to add set CheckBox state upon filling ListBox. This ListBoxis styled to contain CheckBoxes for items:
<Window.Resources>
<Style x:Key="CheckBoxListStyle" TargetType="ListBox">
<Setter Property="SelectionMode" Value="Multiple"/>
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="ListBoxItem">
<Setter Property="Margin" Value="2"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<CheckBox Focusable="False"
IsChecked="{Binding Path=IsSelected,
Mode=TwoWay,
RelativeSource={RelativeSource TemplatedParent}}">
<ContentPresenter />
</CheckBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
So, when I add text in the loop above, the CheckBoxgets created. I, then, need to set the states of those checkboxes, which come from JSON. So, I need something like this:
var list = new List<int> { 1, 2, 3 };
var json = JsonNode.Parse("""
{
"checked": true
}
""");
foreach (var index in list)
{
CheckBox checkBox = null;
var pos = lstColumns.Items.Add(index);
var container = lstColumns.ItemContainerGenerator.ContainerFromIndex(pos);
// Reach checkbox
// ...
// checkBox = ...
// ...
checkBox.IsChecked = json["checked"].GetValue<bool>();
}
And the problem is that container is always null.
Also, it doesn't matter whether I use Loaded or ContentRendered event - in either case container is null.
A High-Level Introduction
The reason that ContainerFromIndex returns null is that the container simply is not realized.
Returns the element corresponding to the item at the given index within the ItemCollection or returns null if the item is not realized.
This is controlled by the ItemContainerGenerator that is responsible for the following actions.
Maintains an association between the data view of a multiple-item control, such as ContainerFromElement and the corresponding UIElement tasks.
Generates UIElement items on behalf of a multiple-item control.
A ListBox is an ItemsControl that exposes the ItemsSource property for binding or assigning a collection.
A collection that is used to generate the content of the ItemsControl. The default is null.
Another option is to simply add items to the Items collection in XAML or code.
The collection that is used to generate the content of the ItemsControl. The default is an empty collection. [...]
The property to access the collection object itself is read-only, and the collection itself is read-write.
The Items property is of type ItemCollection, which is also a view.
If you have an ItemsControl, such as a ListBox that has content, you can use the Items property to access the ItemCollection, which is a view. Because it is a view, you can then use the view-related functionalities such as sorting, filtering, and grouping. Note that when ItemsSource is set, the view operations delegate to the view over the ItemsSource collection. Therefore, the ItemCollection supports sorting, filtering, and grouping only if the delegated view supported them.
You cannot use both ItemsSource and Items at the same time, they are related.
[...] you use either the Items or the ItemsSource property to specify the collection that should be used to generate the content of your ItemsControl. When the ItemsSource property is set, the Items collection is made read-only and fixed-size.
Both ItemsSource and Items either maintain a reference to or bind your data items, these are not the containers. The ItemContainerGenerator is responsible for creating the user interface elements or containers such as ListBoxItem and maintaining the relationship between the data and these items. These containers do not just exist throughout the lifecycle of your application, they get created and destroyed as needed. When does that happen? It depends. Containers are created or realized (using the internal terminology) when they are shown in the UI. That is why you only gain access to a container after it was first shown. How long they actually exist depends on factors like interaction, virtualization or container recycling. By interaction I mean any form of changing the viewport, which is the part of the list that you can actually see. Whenever items are scrolled into view, they need to be realized of course. For large lists with tens of thousands of items, realizing all containers in advance or keeping all containers once they are realized would hit performace and increase memory consumption drastically. That is where virtualization comes into play. See Displaying large data sets for reference.
UI Virtualization is an important aspect of list controls. UI virtualization should not be confused with data virtualization. UI virtualization stores only visible items in memory but in a data-binding scenario stores the entire data structure in memory. In contrast, data virtualization stores only the data items that are visible on the screen in memory.
By default, UI virtualization is enabled for the ListView and ListBox controls when their list items are bound to data.
This implies that containers are deleted, too. Additionally, there is container recycling:
When an ItemsControl that uses UI virtualization is populated, it creates an item container for each item that scrolls into view and destroys the item container for each item that scrolls out of view. Container recycling enables the control to reuse the existing item containers for different data items, so that item containers are not constantly created and destroyed as the user scrolls the ItemsControl. You can choose to enable item recycling by setting the VirtualizationMode attached property to Recycling.
The consequence of virtualization and container recycling is that containers for all items are not realized in general. There are only containers for a subset of your bound or assigned items and they may be recycled or detached. That is why it is dangerous to directly reference e.g. ListBoxItems. Even if virtualization is disabled, you can run into problems like yours, trying to access user interface elements with a different lifetime than your data items.
In essence, your approach can work, but I recommend a different approach that is much more stable and robust and compatible with all of the aforementioned caveats.
A Low-Level View
What is actually happening here? Let us explore the code in medium depth, as my wrists already hurt.
Here is the ContainerFromIndex method in the reference source of .NET.
The for loop in line 931 iterates ItemBlocks using the Next property of the _itemMap.
When your items were not shown, yet in the user interface, they are not realized.
In this case, Next will return an UnrealizedItemBlock (derivative of ItemBlock).
This item block will have a property ItemCount of zero.
The if condition in line 933 will not be met.
This continues until the item blocks are iterated and null is returned in line 954..
Once the ListBox and its items are shown, the Next iterator will return a RealizedItemBlock which has an ItemCount of greater than zero and will therefore yield an item.
How are the containers realized then? There are methods to generate containers.
DependencyObject IItemContainerGenerator.GenerateNext(), see line 230.
DependencyObject IItemContainerGenerator.GenerateNext(out bool isNewlyRealized), see line 239.
These are called in various places, like VirtualizingStackPanel - for virtualization.
protected internal override void BringIndexIntoView(int index), see line 1576, which does exactly what it is called. When an item with a certain index needs to be brought into view, e.g. through scrolling, the panel needs to create the item container in order to show the item in the user interface.
private void MeasureChild(...), see line 8005. This method is used when calculating the space needed to display a ListView, which is influenced by the number and size of its items as needed.
...
Over lots of indirections from a high-level ListBox over its base type ItemsControl, ultimately, the ItemContainerGenerator is called to realize items.
An MVVM Compliant Solution
For all the previously stated issues, there is a simple, yet superior solution. Separate your data and application logic from the user interface. This can be done using the MVVM design pattern. For an introduction, you can refer to the Patterns - WPF Apps With The Model-View-ViewModel Design Pattern article by Josh Smith.
In this solution I use the Microsoft.Toolkit.Mvvm NuGet package from Microsoft. You can find an introduction and a detailed documentation here. I use it because for MVVM in WPF you need some boilerplate code for observable objects and commands that would bloat the example for a beginner. It is a good library to start and later learn the details of how the tools work behind the scenes.
So let us get started. Install the aforementioned NuGet package in a new solution. Next, create a type that represents our data item. It only contains two properties, one for the index, which is read-only and one for the checked state that can be changed. Bindings only work with properties, that is why we use them instead of e.g. fields. The type derives from ObservableObject which implements the INotifyPropertyChanged interface. This interface needs to be implemented to be able to notify that property values changed, otherwise the bindings that are introduced later will not know when to update the value in the user interface. The ObservableObject base type already provides a SetProperty method that will take care of setting a new value to the backing field of a property and automatically notify its change.
using Microsoft.Toolkit.Mvvm.ComponentModel;
namespace RibbonBackstageFillTest
{
public class JsonItem : ObservableObject
{
private bool _isChecked;
public JsonItem(int index, bool isChecked)
{
Index = index;
IsChecked = isChecked;
}
// ...read-only property assumed here.
public int Index { get; }
public bool IsChecked
{
get => _isChecked;
set => SetProperty(ref _isChecked, value);
}
// ...other properties.
}
}
Now we implement a view model for your Root view, which holds the data for the user interface. It exposes an ObservableCollection<JsonItem> property that we use to store the JSON data items. This special collection automatically notifies if any items were added, removed or replaced. This is not necessary for your example, but you I guess it could be useful for you later. You can also replace the whole collection, as we again derived from ObservableObject and use SetProperty. The GetPropertiesCommand is a command, which is just an encapsulated action, an object that performs a task. It can be bound and replaces the Click handler later. The CreateItems method simply creates a list like in your example. The GetProperties is the method where you iterate the list and set your values from JSON. Adapt the code to your needs.
using System.Collections.ObjectModel;
using System.Windows.Input;
using Microsoft.Toolkit.Mvvm.ComponentModel;
using Microsoft.Toolkit.Mvvm.Input;
namespace RibbonBackstageFillTest
{
public class RootViewModel : ObservableObject
{
private ObservableCollection<JsonItem> _jsonItems;
public RootViewModel()
{
JsonItems = CreateItems();
GetPropertiesCommand = new RelayCommand(GetProperties);
}
public ObservableCollection<JsonItem> JsonItems
{
get => _jsonItems;
set => SetProperty(ref _jsonItems, value);
}
public ICommand GetPropertiesCommand { get; }
private ObservableCollection<JsonItem> CreateItems()
{
return new ObservableCollection<JsonItem>
{
new JsonItem(1, false),
new JsonItem(2, true),
new JsonItem(3, false),
new JsonItem(4, true),
new JsonItem(5, false)
};
}
private void GetProperties()
{
foreach (var jsonItem in JsonItems)
{
jsonItem.IsChecked = // ...set your JSON values here.
}
}
}
}
The code-behind of your Root view is now reduced to its essentials, no data anymore.
using Fluent;
using Fluent.Localization.Languages;
using System.Threading;
using System.Windows;
namespace RibbonBackstageFillTest
{
public partial class Root
{
public Root()
{
InitializeComponent();
WindowStartupLocation = WindowStartupLocation.CenterScreen;
ContentRendered += delegate
{
if (Thread.CurrentThread.CurrentUICulture.Name != "en-US")
{
RibbonLocalization.Current.LocalizationMap.Clear();
RibbonLocalization.Current.Localization = new English();
}
};
}
}
}
At last, we create the XAML for the Root view. I have added comments for you to follow along. In essence, we add the new RootViewModel as DataContext and use data-binding to connect our data item collection with the ListBox via the ItemsSource property. Furthermore, we use a DataTemplate to define the appearance of the data in the user interface and bind the Button to a command.
<r:RibbonWindow x:Class="RibbonBackstageFillTest.Root"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:r="urn:fluent-ribbon"
xmlns:local="clr-namespace:RibbonBackstageFillTest"
mc:Ignorable="d"
Title="Backstage Ribbon"
Height="450"
Width="800">
<r:RibbonWindow.DataContext>
<!-- This creates an instance of the root view model and assigns it as data context. -->
<local:RootViewModel/>
</Window.DataContext>
<Window.Resources>
<Style x:Key="CheckBoxListStyle"
TargetType="ListBox">
<Setter Property="SelectionMode" Value="Multiple" />
<!-- This is only used to style the containers, we do not need to change the control template -->
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="ListBoxItem">
<Setter Property="Margin" Value="2" />
</Style>
</Setter.Value>
</Setter>
<!-- An item template is used to define the appearance of a data item. -->
<Setter Property="ItemTemplate">
<Setter.Value>
<!-- We create a data template for our custom item type. -->
<DataTemplate DataType="local:JsonItem">
<!-- The binding will loosely connect the IsChecked property of CheckBox with the IsChecked property of its JsonItem. -->
<!-- The binding is TwoWay by default, meaning that you can change IsChecked in code or in the UI by clicking the CheckBox. -->
<!-- The IsChecked value will always be synchronized in the view and view model. -->
<CheckBox Focusable="False"
IsChecked="{Binding Path=IsChecked}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</r:RibbonWindow.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<r:Ribbon Grid.Row="0">
<!-- Backstage -->
<r:Ribbon.Menu>
<r:Backstage>
<r:BackstageTabControl>
<r:BackstageTabItem Header="Columns">
<Grid>
<!-- No need for a name anymore, we do not need to access controls. -->
<!-- The binding loosely connects the JsonItems collection with the ListBox. -->
<ListBox ItemsSource="{Binding JsonItems}"
Style="{StaticResource CheckBoxListStyle}"/>
</Grid>
</r:BackstageTabItem>
</r:BackstageTabControl>
</r:Backstage>
</r:Ribbon.Menu>
<!-- Tabs -->
<r:RibbonTabItem Header="Home">
<r:RibbonGroupBox Header="ID">
<!-- Instead of a Click event handler, we bind a command in the view model. -->
<r:Button Size="Large"
LargeIcon="pack://application:,,,/RibbonBackstageFillTest;component/img/PropertySheet.png"
Command="{Binding GetPropertiesCommand}"
Header="Properties"/>
</r:RibbonGroupBox>
</r:RibbonTabItem>
</r:Ribbon>
</Grid>
</r:RibbonWindow>
Now what is the difference? The data and your application logic is separated from the user interface. The data is always there in the view model, regardless of an item container. In fact, your data does not even know that there is a container or a ListBox. Whether the backstage is open or not, does not matter anymore, as you directly act on your data, not the user interface.
A Quicker And Dirtier Solution
I do not recommend this solution, it is just a quick and dirty solution apart from MVVM that might be easier to follow for you after you saw how to do it right. It uses the JsonItem type from before, but this time without an external library. Now you see what INotifyPropertyChanged does under the hood.
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace RibbonBackstageFillTest
{
public class JsonItem : INotifyPropertyChanged
{
private bool _isChecked;
public JsonItem(int index, bool isChecked)
{
Index = index;
IsChecked = isChecked;
}
// ...read-only property assumed here.
public int Index { get; }
public bool IsChecked
{
get => _isChecked;
set
{
if (_isChecked == value)
return;
_isChecked = value;
OnPropertyChanged();
}
}
// ...other properties.
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
In your code-behind of the Root view, just create a field _jsonItems that stores the items. This field is used to access the list later in order to change the IsChecked values.
using Fluent;
using Fluent.Localization.Languages;
using System.Collections.Generic;
using System.Threading;
using System.Windows;
namespace RibbonBackstageFillTest
{
public partial class Root
{
private List<JsonItem> _jsonItems;
public Root()
{
InitializeComponent();
WindowStartupLocation = WindowStartupLocation.CenterScreen;
ContentRendered += delegate
{
if (Thread.CurrentThread.CurrentUICulture.Name != "en-US")
{
RibbonLocalization.Current.LocalizationMap.Clear();
RibbonLocalization.Current.Localization = new English();
}
};
_jsonItems = new List<JsonItem>
{
new JsonItem(1, false),
new JsonItem(2, true),
new JsonItem(3, false),
new JsonItem(4, true),
new JsonItem(5, false)
};
lstColumns.ItemsSource = _jsonItems;
}
private void OnGetProperties(object sender, RoutedEventArgs e)
{
foreach (var jsonItem in _jsonItems)
{
jsonItem.IsChecked = // ...set your JSON value.
}
}
}
}
At last for the Root view not much changes. We copy the style with the data template from the MVVM sample and set it to the ListBox. It will just behave the same, as your data is not dependent on view containers.
<r:RibbonWindow.Resources>
<Style x:Key="CheckBoxListStyle"
TargetType="ListBox">
<Setter Property="SelectionMode" Value="Multiple" />
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="ListBoxItem">
<Setter Property="Margin" Value="2" />
</Style>
</Setter.Value>
</Setter>
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate DataType="local:JsonItem">
<CheckBox Focusable="False"
IsChecked="{Binding Path=IsChecked}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</r:RibbonWindow.Resources>
<ListBox x:Name="lstColumns"
Style="{StaticResource CheckBoxListStyle}"/>
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));
}
}
I'm currently working on a small Video Poker app, partially because it's fun & because I needed a project for school.
Unless I missed something, my logic should be done, but now, as I'm turning my attention to the UI aspect of the app, I've hit a snag.
What I need/want my ListView to do is this:
Not interact with mouseovers/mouse clicks performed by the user
Be filled through code, the items need to be replaced each time the bet changes
When the user has a winning hand, highlight it in the ListView by changing the BackGround property.
So far I've had "some" success, meaning I've been able to have it work so that I can select an item through mouse-clicks, after which the color changed, however I couldn't get it to work by changing the SelectedItem to the winning hand.
I've tried just about anything I can think of & I've hit a dead end.
I'm fairly sure it's somehow achieved through Binding data, but all my efforts so far have failed. I still lack quite a bit of both knowledge & experience when it comes to Databinding XAML elements.
If anyone here could help me out with a neat/clean/efficient way to achieve this, it would be greatly appreciated.
XAML:
<ListView x:Name="lvTest" HorizontalAlignment="Left" Height="200" VerticalAlignment="Top" Width="160" Margin="10,10,0,0">
<ListView.Resources>
<Style TargetType="GridViewColumnHeader">
<Setter Property="Visibility" Value="Collapsed" />
</Style>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<GridViewRowPresenter />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.Resources>
<ListView.View>
<GridView>
<GridViewColumn Width="105" DisplayMemberBinding="{Binding HandNaam}"/>
<GridViewColumn Width="45" DisplayMemberBinding="{Binding Betaling}"/>
</GridView>
</ListView.View>
</ListView>
C#:
List<Hand> hands = new List<Hand>();
//Bet has been changed, (re-)populate the ListView
void Betchanged()
{
hands.Clear();
lvTest.ItemsSource = null;
for (int i = 0; i <= 8; i++)
{
hands.Add(new Hand(Hand.hands[i], bet));
}
lvTest.ItemsSource = hands;
}
//Game ends
//After checking for a winning hand, highlight that hand in the ListView
private void EndGame()
{
//Do some stuff
//Check if there's a winning hand
int winnings;
Hand hand = null;
winnings = Poker.FindWin(splK, hands, out hand); //out hand is of type Hand
//If there's a winning hand, highlight it in the ListView
if (hand != null)
{
foreach (Hand h in lvTest.Items)
{
if (h.HandName == hand.HandName)
{
//This seems like the most logical (maybe not most efficient) way to find the correct item in the ListView
//Here's where the Background property for the ListViewItem should be changed
}
}
}
//Do some more stuff
}
PS: The current Template style in my XAML is what's overriding my ListViewItem's hover/click interactions.
Edit: Here's a screenshot with a possible scenario when the Background for a ListViewItem should be changed:
Your ItemsSource in order to notify the control via the Binding that it's contents have changed, needs to implement INotifyCollectionChanged. At the moment, the most common generic collection to implement this interface is ObservableCollection<T>.
hands should be an ObservableCollection<Hand> rather than a List<Hand>.
Edit based on comment:
WPF Binding works on notifications of changes. For properties, this means implementing INotifyPropertyChanged on classes that need to notify that their properties have changed. Your Hand class should implement INotifyPropertyChanged for when the specific value you want to change has changed.
Presentation logic should be handled by the View class (or code related to the View like a Converter) so you should bind to your Model property value and convert it to a Brush object using a Converter.
You could just expose a Brush on your Model object but that would be breaking MVVM pattern as a ViewModel is not supposed to have View-specific/presentation technology classes embedded in them in order to be presentation technology agnostic.
Second edit based on second comment:
Converters are code that convert values from one type to the other by implementing IValueConverter interface ( MSDN : IValueConverter documentation ). They need to be declared as resources in your XAML and referenced using StaticResource directives in the Binding.
An excellent tutorial on this (and other basic WPF subjects) can be found at WPFTutorial.com - Value Converters
I have set up a complete application using C# .NET 4, Prism and Unity that implements the INavigationAware interface on the ViewModel of an MVVM pattern. My window (Shell.xaml) is very simple at the moment (static string for RegionName to avoid magic strings):
Shell.xaml
<Grid>
<ContentControl prism:RegionManager.RegionName="{x:Static Infrastructure:RegionNames.ContentRegion}" />
</Grid>
Each of my views contains buttons that allow the user to open another view using a centralized CompositeCommand to which I attach a DelegateCommand in the Shell like so:
ViewA.xaml
<Button Name="AcceptButton" Content="Accept"
Command="{x:Static Infrastructure:ApplicationCommands.NavigateCommand}"
CommandParameter="{x:Type Concrete:ViewB}"
ApplicationCommands.cs
public static class ApplicationCommands {
public static CompositeCommand NavigateCommand = new CompositeCommand();
}
ShellViewModel.cs
public ShellViewModel(IRegionManager regionManager) {
_regionManager = regionManager;
NavigateCommand = new DelegateCommand<object>(Navigate, CanNavigate);
ApplicationCommands.NavigateCommand.RegisterCommand(NavigateCommand);
}
private void Navigate(object navigatePath) {
if (navigatePath != null) {
_regionManager.RequestNavigate(RegionNames.ContentRegion, navigatePath.ToString(), NavigationCallback);
}
}
I have several more views tied in and the navigation is working great. Now comes the changes that are failing. Having random buttons on each screen is really ineffective and contrary to good design so I am trying to pull the buttons out for a centralized toolbar. I have pulled the ViewA.xaml button code out of the ViewA.xaml file ( which contains much more content but not shown for overkill reasons ) and put it into a ViewAButonn.xaml file. I then modified the Shell.xaml and add a second region:
Modified Shell.xaml
<Grid>
<ContentControl prism:RegionManager.RegionName="{x:Static Infrastructure:RegionNames.ContentRegion}" />
<ContentControl prism:RegionManager.RegionName="{x:Static Infrastructure:RegionNames.NavRegion}" />
</Grid>
I can add my new ViewAButton.xaml to the region without any issue and when I click it the View contents are then placed properly into the ContentRegion.
My issue arises here though. My first screen is a TOS agreement screen that cannot display the toolbar until "Accept" button is clicked. I am terrified at the thought of handling this in the ViewModel as I have it properly decoupled right now. Do I modify the View to contain a property that can be read during navigation to hide the region? If so where in the navigation process can I get access to the View that is activated by Unity? All of my views implement an IView interface that just exposes an IVewModel as per the MSDN instruction on setting up a proper prism MVVM. How can I hide this new toolbar on the TOS acceptance screen?
Based on my understanding it would not be possible to collapse a Region. The View would get Hidden but the space where the Region is located would remain empty.
If you want the TOS screen to fill the entire window, you could create two levels of Regions:
For example, you could declare a "WindowRegion" Region in the Shell View which fills the complete Window and where the TOS screen and a defined "CustomRegionsView" would get registered.
Then, the CustomRegionsView would define both Regions you mentioned above so the App can navigate between the TOS screen and any View with the two regions you described.
Therefore, when "Accept" button is clicked from the TOS screen, Navigation to the CustomRegionsView should be performed as also to the particular Views which are registered on the ContentRegion and NavRegion.
I hope you find this helpful.
It’s worth having a closer look at the “Prism for WPF Reference Implementation”, in particular, Shell.xaml in Line 173 and onwards. This’ll show you that it’s indeed possible to collapse a Prism region.
<ContentControl x:Name="ActionContent" prism:RegionManager.RegionName="{x:Static inf:RegionNames.ActionRegion}">
<ContentControl.Template>
<ControlTemplate TargetType="ContentControl">
<Grid>
<Controls:RoundedBox />
<ContentPresenter Margin="10,0,10,0" Content="{TemplateBinding Content}" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="HasContent" Value="false">
<Setter Property="Visibility" Value="Collapsed" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</ContentControl.Template>
</ContentControl>
They combine this with a data converter that is being made use of within the row definitions of a grid layout:
<Grid.Resources>
<StockTraderRI:VisibilityToStarHeightConverter x:Key="VisibilityToStarHeightConverter" />
</Grid.Resources>
<Grid.Rows>
…
<RowDefinition Height="{Binding Visibility, ElementName=ActionContent, Converter={StaticResource VisibilityToStarHeightConverter}, ConverterParameter=5}" />
…
where the converter itself is defined as
public class VisibilityToStarHeightConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
if ((Visibility)value == Visibility.Collapsed) {
return new GridLength(0, GridUnitType.Star);
} else {
if (parameter == null) {
throw new ArgumentNullException("parameter");
}
return new GridLength(double.Parse(parameter.ToString(), culture), GridUnitType.Star);
}
}
…
I’ve omitted a couple of things here, but the picture is clear: you don’t actually need to make use of the zindex or other workarounds.
Again, as mentioned above, the implementation I pasted here is not mine, but part of the Prism Quick Starts over at Microsoft.
I'm struggling for about 14 days now with a simple task: In database, I have definitions for hardware categories. For example :
HDD
Internal
External
Flash
This list is in database defined like this:
[ID - ParrentID - Name] : 1 - 0 - HDD, 2 - 1 - Internal, 3 - 1 - External, 4 - 1 - Flash.
Through Entity Framework I get these rows into my application. From this flat data I then create structured object which is my DataModel. This model is defined as follows :
public class Category
{
private int _id = -1;
private string _name = "";
private List<Category> _subCategories = null;
// property getters and setters, constructors, and bool HasSubCategories
}
Now, from these I create ViewModel called SubCategoryViewModel to which is binded my TreeView. So, I can view my categories in treeview and with my defined and maintained hierarchy. This works just fine. In SubCategoryViewModel is defined a Command through Attached Behavior for MouseDoubleClick which is also binded to TreeView. So, when user doubleclicks on Item, in SubViewCategoryModel defined method will execute particular code. List of SubCategoryViewModel is nested in HWDocumentViewModel which is a main ViewModel for my window.
What I need now is obvious : When user doubleclicks on item in TreeView, I need to load items from database and show them in ListView. My opinion is, that in HWDocumentViewModel I need to define an collection of Items and load them accordingly to selected category in ListView. But, I don't know how to execute a method on HWDocumentViewModel from SubCategoryViewModel. Because : TreeView is binded to list of SubCategoryViewModel items, so when DoubleClick occurs, the method on SubCategoryViewModel is executed. I'm searching for a way, how to execute a method on main ViewModel (HWDocumentViewModel).
I tried this approach :
I created a property : public static SubCategoryViewModel SelectedCategory on HWDocumentViewModel. When doubleclick occurs, I set this property from SubCategoryViewModel as this. So, in this property is object, which executed doubleclick event delegate. Great, now I have in HWDocumentView model an object, which user selected.
So, I need to load items to ListView. But, will I load them from method in SubCategoryViewModel ? I don't think so. Instead I should load them from Main View Model by creating a ViewModel for them and bind it to ListView, right ? But, how can I from SubCategoryViewModel call a method in HWDocumentViewModel ? Should I write a static method
on a HWDocumentViewModel which will be accessible from SubCategoryViewModel ?
Or is there a way, how to call Command defined on HWDocumentViewModel from SubCategoryViewModel ?
Or generally, did I take a right approach to create a Warehouse-like application in WPF ?
Thanks a lot.
EDIT: XAML for my TreeView looks like this :
<TreeView x:Name="tvCategories" Background="White" ItemsSource="{Binding Categories}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="behaviors:MouseDoubleClick.Command" Value="{Binding MouseDoubleClickCommand}" />
<Setter Property="behaviors:MouseDoubleClick.CommandParameter" Value="{Binding}" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type localvm:SubCategoryViewModel}" ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding CategoryName}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
I'm not sure I see the problem. You have a tree of subcategories and when one is selected, the appropriate SubCategoryViewModel sets itself as SelectedCategory on the main HWDocumentViewModel. That seems like a reasonable approach.
So why do you need to call a command? Why can't you just load the new list in HWDocumentViewModel in response to a change of its SelectedCategory property (ie in the setter)?
If you really must use a command to invoke the load, then simply keep a reference to your main HWDocumentViewModel in each SubCategoryViewModel, and invoke the command with a simple:
_mainViewModel.LoadCategoryCommand.Execute();
With MVVM and trying to communicate between View and ViewModel or between ViewModels a publisher/Subscriber setup works well or a messaging paradigm like what's found in MVVMLight or Prism. I posted an answer on MVVM Light's messaging setup here
In the message you can send an object that holds any data you would like to send back and forth between the view models.
I highly recomend using a framework when working with mvvm it makes like much easier. MVVM Framework Comparison is a link to an answer that goes through a comparison of some of the major frameworks.