How to write a DataTemplate that works with a VisualStateGroup in WinUI 3? - c#

I've been trying to pass collections of VisualStateGroup objects to various ContentControls in WinUI 3 (1.1.5) but I keep getting a 'Value does not fall within the expected range.' error. I think I've defined an appropriate DataTemplate, but nothing seems to work. Here's a simple example Page from a new Template Studio Winui 3 project:
public sealed partial class MainPage : Page
{
public MainViewModel ViewModel { get; }
public ShellPage ShellPage { get; }
public MainPage()
{
ViewModel = App.GetService<MainViewModel>();
InitializeComponent();
ShellPage = App.GetService<ShellPage>();
var rootGrid = (ShellPage.IsLoaded) ? VisualTreeHelper.GetChild(ShellPage.NavigationViewControl, 0) as Grid : null;
if (rootGrid != null)
{
var listOfVisualStateGroups = VisualStateManager.GetVisualStateGroups(rootGrid);
if (listOfVisualStateGroups?.Count > 0) cControl.Content = listOfVisualStateGroups[0];
}
}
}
<Page
x:Class="TestCollection.Views.MainPage"
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"
mc:Ignorable="d">
<Grid x:Name="ContentArea">
<ContentControl x:Name="cControl">
<ContentControl.ContentTemplate>
<DataTemplate x:DataType="VisualStateGroup">
<TextBlock Text="{x:Bind Name}" />
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
</Grid>
</Page>
All this does is fetch the list of VisualStateGroups attached to the NavigationViewControl of a vanilla WinUI 3 project. The first group in the list is then displayed in the ContentControl cControl by assigning it to Content. But the assignment causes the error.
I gather there is something wrong with the DataTemplate, but I can't figure out what. A similar problem occurs if I try to create a templated ListViewItem with a VisualStateGroup object.
This is a significant refinement of a question I asked a few days ago (please see: Bound ListView won't accept List<VisualStateGroup> as ItemsSource in WinUI 3. Any idea why?, if curious). My purpose in asking is to better understand the mechanics and requirements of templating. Any help is greatly appreciated.
Update: 10/2/2022
I haven't found an explanation for the error but I have found that simply wrapping the VisualStateGroup and VisualState classes lets me use collections of the new classes as I would expect. Thing1 and Thing2 are just copies of VisualStateGroup and VisualState (both of which are sealed and can't be inherited directly).
public sealed partial class MainPage : Page
{
public MainViewModel ViewModel { get; }
public ShellPage ShellPage { get; }
public List<Thing1> Things { get; set; } = new();
public List<Thing2> OtherThings { get; set; } = new();
public MainPage()
{
ViewModel = App.GetService<MainViewModel>();
InitializeComponent();
ShellPage = App.GetService<ShellPage>();
var rootGrid = (ShellPage.IsLoaded) ? VisualTreeHelper.GetChild(ShellPage.NavigationViewControl, 0) as Grid : null;
if (rootGrid != null)
{
var listOfVisualStateGroups = VisualStateManager.GetVisualStateGroups(rootGrid);
foreach (var group in listOfVisualStateGroups) Things.Add(new(group));
if (listOfVisualStateGroups?.Count > 0)
foreach (var state in listOfVisualStateGroups[0].States) OtherThings.Add(new(state));
}
}
}
// copy of VisualStateGroup
public partial class Thing1
{
public VisualState CurrentState { get; }
public CoreDispatcher Dispatcher { get; }
public DispatcherQueue DispatcherQueue { get; }
public string Name { get; }
public IList<VisualState> States { get; }
public IList<VisualTransition> Transitions { get; }
public Thing1(VisualStateGroup group)
{
CurrentState = group.CurrentState;
Dispatcher = group.Dispatcher;
DispatcherQueue = group.DispatcherQueue;
Name = group.Name;
States = group.States;
Transitions = group.Transitions;
}
}
// copy of VisualState
public partial class Thing2
{
public CoreDispatcher Dispatcher { get; }
public DispatcherQueue DispatcherQueue { get; }
public string Name { get; }
public SetterBaseCollection Setters { get; }
public IList<StateTriggerBase> StateTriggers { get; }
public Storyboard Storyboard { get; set; }
public Thing2(VisualState state)
{
Dispatcher = state.Dispatcher;
DispatcherQueue = state.DispatcherQueue;
Name = state.Name;
Setters = state.Setters;
StateTriggers = state.StateTriggers;
Storyboard = state.Storyboard;
}
}
<Page
x:Class="TestCollection.Views.MainPage"
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:views="using:TestCollection.Views"
mc:Ignorable="d">
<Grid x:Name="ContentArea">
<StackPanel Orientation="Horizontal">
<ListView Margin="0,0,20,20" BorderThickness="1" BorderBrush="Black" Header="Things (VisualStateGroups)"
ItemsSource="{x:Bind Things}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="views:Thing1">
<TextBlock Text="{x:Bind Name}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ListView Margin="0,0,20,20" BorderThickness="1" BorderBrush="Black" Header="OtherThings (VisualStates)"
ItemsSource="{x:Bind OtherThings}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="views:Thing2">
<TextBlock Text="{x:Bind Name}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</Grid>
</Page>
Any idea why an ObservableCollection<Thing1> works but an ObservableCollection<VisualStateGroup> doesn't?

Related

WPF Usercontrol Bindings with MVVM ViewModel not working

I've spent some time trying to solve this problem but couldn't find a solution.
I am trying to bind commands and data inside an user control to my view model. The user control is located inside a window for navigation purposes.
For simplicity I don't want to work with Code-Behind (unless it is unavoidable) and pass all events of the buttons via the ViewModel directly to the controller. Therefore code-behind is unchanged everywhere.
The problem is that any binding I do in the UserControl is ignored.
So the corresponding controller method is never called for the command binding and the data is not displayed in the view for the data binding. And this although the DataContext is set in the controllers.
Interestingly, if I make the view a Window instead of a UserControl and call it initially, everything works.
Does anyone have an idea what the problem could be?
Window.xaml (shortened)
<Window x:Class="Client.Views.MainWindow"
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:local="clr-namespace:Client.Views"
mc:Ignorable="d">
<Window.Resources>
<local:SubmoduleSelector x:Key="TemplateSelector" />
</Window.Resources>
<Grid>
<StackPanel>
<Button Command="{Binding OpenUserControlCommand}"/>
</StackPanel>
<ContentControl Content="{Binding ActiveViewModel}" ContentTemplateSelector="{StaticResource TemplateSelector}">
<ContentControl.Resources>
<DataTemplate x:Key="userControlTemplate">
<local:UserControl />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</Grid>
</Window>
MainWindowViewModel (shortened)
namespace Client.ViewModels
{
public class MainWindowViewModel : ViewModelBase
{
private ViewModelBase mActiveViewModel;
public ICommand OpenUserControlCommand { get; set; }
public ViewModelBase ActiveViewModel
{
get { return mActiveViewModel; }
set
{
if (mActiveViewModel == value)
return;
mActiveViewModel = value;
OnPropertyChanged("ActiveViewModel");
}
}
}
}
MainWindowController (shortened)
namespace Client.Controllers
{
public class MainWindowController
{
private readonly MainWindow mView;
private readonly MainWindowViewModel mViewModel;
public MainWindowController(MainWindowViewModel mViewModel, MainWindow mView)
{
this.mViewModel = mViewModel;
this.mView = mView;
this.mView.DataContext = mViewModel;
this.mViewModel.OpenUserControlCommand = new RelayCommand(ExecuteOpenUserControlCommand);
}
private void OpenUserControlCommand(object obj)
{
var userControlController = Container.Resolve<UserControlController>(); // Get Controller instance with dependency injection
mViewModel.ActiveViewModel = userControlController.Initialize();
}
}
}
UserControlSub.xaml (shortened)
<UserControl x:Class="Client.Views.UserControlSub"
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:local="clr-namespace:Client.Views"
xmlns:viewModels="clr-namespace:Client.ViewModels"
mc:Ignorable="d">
<Grid>
<ListBox ItemsSource="{Binding Models}" SelectedItem="{Binding SelectedModel}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Attr}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel>
<Button Command="{Binding Add}">Kategorie hinzufügen</Button>
</StackPanel>
</Grid>
</UserControl>
UserControlViewModel (shortened)
namespace Client.ViewModels
{
public class UserControlViewModel : ViewModelBase
{
private Data _selectedModel;
public ObservableCollection<Data> Models { get; set; } = new ObservableCollection<Data>();
public Data SelectedModel
{
get => _selectedModel;
set
{
if (value == _selectedModel) return;
_selectedModel= value;
OnPropertyChanged("SelectedModel");
}
}
public ICommand Add { get; set; }
}
}
UserControlController (shortened)
namespace Client.Controllers
{
public class UserControlController
{
private readonly UserControlSub mView;
private readonly UserControlViewModel mViewModel;
public UserControlController(UserControlViewModel mViewModel, UserControlSub mView)
{
this.mViewModel = mViewModel;
this.mView = mView;
this.mView.DataContext = mViewModel;
this.mViewModel.Add = new RelayCommand(ExecuteAddCommand);
}
private void ExecuteAddCommand(object obj)
{
Console.WriteLine("This code gets never called!");
}
public override ViewModelBase Initialize()
{
foreach (var mod in server.GetAll())
{
mViewModel.Models.Add(mod);
}
return mViewModel;
}
}
}

Using TabItemTemplateSelector in WinUI 3 in conjunction with a TemplateSelector resulting in weirdly nested controls

Introduction:
I'm currently working on an app, where data should be displayed in a TabView. By default, a "home" tab will be open, displaying a Listbox. Upon clicking an entry in this box, a detail view will open in a new tab.
The app is created in an MVVM-architecture. A "MainViewModel" contains a collection with the ViewModels populating the TabView. I'm trying to use a DataTemplateSelector to switch the DataTemplates based on the ViewModel class.
The project is using the following dependencies:
<PackageReference Include="CommunityToolkit.Mvvm" Version="7.0.2" />
<PackageReference Include="CommunityToolkit.WinUI.UI.Controls" Version="7.0.2" />
<PackageReference Include="Microsoft.ProjectReunion" Version="0.5.7" />
<PackageReference Include="Microsoft.ProjectReunion.Foundation" Version="0.5.7" />
<PackageReference Include="Microsoft.ProjectReunion.WinUI" Version="0.5.7" />
Sample Code
The MainWindow.xaml.cs contains the ViewModels and the Template Selector
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
namespace TestingApp
{
public sealed partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
}
}
public class TabItemDataTemplateSelector : DataTemplateSelector
{
public DataTemplate HomeTemplate { get; set; }
public DataTemplate DetailTemplate { get; set; }
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
switch (item)
{
case HomeTabViewModel:
return HomeTemplate;
case DetailTabViewModel:
return DetailTemplate;
default:
throw new ArgumentException("Invalid ViewModel type supplied as item");
}
}
}
public class MainWindowViewModel : ObservableObject
{
private ObservableCollection<ObservableObject> tabItems;
public ObservableCollection<ObservableObject> TabItems { get => tabItems; set => SetProperty(ref tabItems, value); }
public MainWindowViewModel()
{
TabItems = new ObservableCollection<ObservableObject>();
TabItems.Add(new HomeTabViewModel());
TabItems.Add(new DetailTabViewModel());
TabItems.Add(new DetailTabViewModel());
}
}
public class HomeTabViewModel : ObservableObject
{
public string Header { get; set; } = "Home";
public string Detail { get; set; } = "I'm the HomeView";
}
public class DetailTabViewModel : ObservableObject
{
public string Title { get; set; } = "Detail"; // Different Name to make the classes a bit different
public string Detail { get; set; } = "Hello from the Details!";
}
}
The MainWindow.xaml contains only a TabView
<Window
x:Class="TestingApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:TestingApp"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid>
<Grid.DataContext>
<local:MainWindowViewModel />
</Grid.DataContext>
<Grid.Resources>
<DataTemplate x:Key="HomeTemplate">
<TabViewItem Header="{Binding Header}" />
</DataTemplate>
<DataTemplate x:Key="DetailTemplate">
<TabViewItem Header="{Binding Title}" />
</DataTemplate>
<local:TabItemDataTemplateSelector x:Key="TemplateSelector" DetailTemplate="{StaticResource DetailTemplate}" HomeTemplate="{StaticResource HomeTemplate}" />
</Grid.Resources>
<TabView TabItemsSource="{Binding TabItems}" TabItemTemplateSelector="{StaticResource TemplateSelector}">
</TabView>
</Grid>
</Window>
Result
The above code creates a weirdly nested result, where it seems like the template is applied multiple times to the header and the content, resulting in TabViewItems all over the place:
Desired result
What's odd to me, is that when I use an "inline" TabItemTemplate, the result is fine, with working bindings.
<Grid>
<Grid.DataContext>
<local:MainWindowViewModel />
</Grid.DataContext>
<TabView TabItemsSource="{Binding TabItems}">
<TabView.TabItemTemplate>
<DataTemplate>
<TabViewItem Header="Tab">
<TextBlock Text="{Binding Detail}" />
</TabViewItem>
</DataTemplate>
</TabView.TabItemTemplate>
</TabView>
</Grid
Question
How can I use the TemplateSelector to correctly create TabViewItems, where the Header and Content is based on the DataTemplate? While searching for similar problems, I found solutions for older projects, which seemed to work as desired when simply nesting a TabViewItem in the DataTemplate, which cleary isn't the case here.
Update: Signficantly less hideous
Use a ContentControl with a template selector, inside a DataTemplate.
<DataTemplate x:Key="SelectorWrapper"
x:DataType="t:Type">
<TabViewItem>
<TabViewItem.Header>
<ContentControl Content="{x:Bind}"
ContentTemplateSelector="{StaticResource HeaderTemplateSelector}" />
</TabViewItem.Header>
<ContentControl Content="{x:Bind}"
ContentTemplateSelector="{StaticResource TemplateSelector}" />
</TabViewItem>
</DataTemplate>
Original awfulness
Thought I'd drop this in, in case anyone just needs template selection. Pretty filthy workaround, but it functions (at least for me) without a huge amount of change. Should be able to do some minor copy/paste to back out, whenever the bug is fixed.
Summary: use a single DataTemplate which contains a subclassed TabViewItem, instead of an actual DataTemplateSelector. The subclass acts as a selector, as well as a TabViewItem. Enables DataTemplates to be crafted in XAML, with the same bindings etc.
Related GitHub issue: https://github.com/microsoft/microsoft-ui-xaml/issues/5852
Base ViewModel for tabs
TabItem.cs
Add your preferred flavour of INPC
public abstract class TabItem {
public object Header { get; set; }
public object Icon { get; set; }
public bool IsClosable { get; set; }
}
Subclassed TabViewItem
TabViewItemSelector.xaml
<TabViewItem x:Class="TabViewTest.TabViewItemSelector"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="using:TabViewTest">
<TabViewItem.Resources>
<!--Header Templates-->
<DataTemplate x:Name="Tab1HeaderTemplate">
<TextBlock Text="Win32 Apps" />
</DataTemplate>
<DataTemplate x:Name="Tab2HeaderTemplate"
x:DataType="l:Tab2ViewModel">
<TextBlock Text="{x:Bind DisplayName, Mode=OneWay}" />
</DataTemplate>
<!--Tab Content Templates-->
<DataTemplate x:Name="Tab1Template"
x:DataType="l:Tab1ViewModel">
<l:APageOrSomething ViewModel="{x:Bind}" />
</DataTemplate>
<DataTemplate x:Name="Tab2Template"
x:DataType="l:Tab2ViewModel">
<l:RepeatPage ViewModel="{x:Bind}" />
</DataTemplate>
</TabViewItem.Resources>
</TabViewItem>
Code for subclassed TabViewItem
TabViewItemSelector.xaml.cs
public sealed partial class TabViewItemSelector : TabViewItem {
private DataTemplate _tabTemplate;
private DataTemplate _headerTemplate;
public TabViewItemSelector() {
InitializeComponent();
}
// DependencyProperty update callback handles template load. Never cleans up after itself.
private static void SelectOrClearTemplate(DependencyObject d, DependencyPropertyChangedEventArgs e) {
if (d is not TabViewItemSelector selector || selector == null) return;
// Create the content
if (e.NewValue != null && e.NewValue is TabItem tab && tab != null) {
// Generate header from template, if not set previously
if (tab.Header == null) {
selector._headerTemplate = tab switch {
Tab1ViewModel => selector.Tab1HeaderTemplate,
Tab2ViewModel => selector.Tab2HeaderTemplate,
_ => throw new NotImplementedException()
};
var headerArgs = new ElementFactoryGetArgs { Data = selector._headerTemplate, Parent = selector };
tab.Header = selector._headerTemplate.GetElement(headerArgs);
}
// Generate tab content
selector._tabTemplate = tab switch {
Tab1ViewModel => selector.Tab1Template,
Tab2ViewModel => selector.Tab2Template,
_ => throw new NotImplementedException()
};
var tabArgs = new ElementFactoryGetArgs { Data = selector._tabTemplate, Parent = selector };
selector.Content = selector._tabTemplate.GetElement(tabArgs);
}
}
public object TabContentSource {
get => GetValue(TabContentSourceProperty);
set => SetValue(TabContentSourceProperty, value);
}
public static readonly DependencyProperty TabContentSourceProperty = DependencyProperty
.Register(nameof(TabContentSource), typeof(object), typeof(TabViewItemSelector), new PropertyMetadata(null, SelectOrClearTemplate));
}
Page with TabView - TabPage.xaml
<Page x:Class="TabViewTest.TabPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:v="using:TabViewTest">
<Page.Resources>
<DataTemplate x:Key="TabViewItemSelectorTemplate"
x:DataType="v:TabItem">
<v:TabViewItemSelector Header="{x:Bind Header, Mode=OneWay}"
IconSource="{x:Bind (IconSource)Icon, Mode=OneWay}"
IsClosable="{x:Bind IsClosable}"
TabContentSource="{x:Bind}" />
</DataTemplate>
</Page.Resources>
<TabView x:Name="TabView"
TabItemsSource="{x:Bind Tabs, Mode=OneWay}"
TabItemTemplate="{StaticResource TabViewItemSelectorTemplate}" />
</Page>
Code for Page with TabView - TabPage.xaml.cs
public sealed partial class TabPage : Page {
public ObservableCollection<TabItem> Tabs { get; set; } = new();
public TabPage() {
InitializeComponent();
// First tab - quick and easy
var tab1 = new Tab1ViewModel() {
Header = "Basic header",
Icon = new SymbolIconSource { Symbol = Symbol.ProtectedDocument },
};
// Second tab header - Tab2ViewModel.DisplayName
var tab2 = new Tab2ViewModel {
DisplayName = "Second tab",
Icon = new SymbolIconSource { Symbol = Symbol.AddFriend },
};
// Third tab - this workaround even supports terribleness
var tab3 = new Tab2ViewModel() {
Header = new TextBox { Text = "Textboxes for everyone!" },
Icon = new SymbolIconSource { Symbol = Symbol.People },
};
// And awaaay we go.
Tabs.Add(tab1);
Tabs.Add(tab2);
Tabs.Add(tab3);
}
}
public class Tab1ViewModel : TabItem { }
public class Tab2ViewModel : TabItem {
public string DisplayName { get; set; }
}

Dynamically create TextBoxes and bind them to a list

I am trying to implement a create mask, on which a user can create a new technology with different versions (e.g: .NET with versions 4.5.2 and 4.6 etc.). For that I want the user to be able to dynamically add text boxes for additional versions.
A requirement for this is to use the MVVM Pattern, which i'm fairly new to. I created the following classes for this:
Entitiy Classes (using Entity Framework)
public class Tech : EntityBase
{
// Properties
public string TechName { get; set; }
// Navigation Properties
public virtual ICollection<TechVersion> TechVersions{ get; set; }
}
public class TechVersion : EntityBase
{
// Properties
public string VersionNumber { get; set; }
// Foreign Keys
//public int TechId{ get; set; }
// Navigation Properties
public virtual Tech Tech{ get; set; }
}
Technology View Model
public class TechnologyUpdateViewModel : ObservableObject
{
private string _technologyName;
public string TechnologyName
{
get
{
return _technologyName;
}
set
{
if (_technologyName != value)
{
_technologyName = value;
OnPropertyChanged("TechnologyName");
}
}
}
private ICommand _add;
public ICommand Add
{
get
{
if (_add == null)
{
_add = new RelayCommand(e => CreateTechnology());
}
return _add;
}
set
{
if (_add != value)
{
_add = value;
OnPropertyChanged("Add");
}
}
}
private void CreateTechnology()
{
// Add new Technology from ViewModel Properties
}
}
I am however having trouble with the XAML Code. Binding the Technology Name and adding a new Technology with just the name works fine (Textbox + Button).
But how do I get the dynamically created Textboxes and bind them to the ViewModel?
I've tried the approach I found here
For this I added public ObservableCollection<string> Versions { get; set; }
But most solutions try to create controls from an already filled list. I am trying to create text boxes and bind them to an empty list.
TechnologyAdd.xaml
<UserControl x:Class="Presentation.Views.TechnologyAddOrEdit"
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:local="clr-namespace:Presentation.Views"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<StackPanel>
<StackPanel MaxWidth="250">
<DockPanel>
<TextBlock>Name: </TextBlock>
<TextBox Text="{Binding TechnologyName}" Margin="10,0,0,0" />
</DockPanel>
<!-- Approach from the Link -->
<ItemsControl>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<ItemsControl ItemsSource="{Binding Versions}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Content="Save" Command="{Binding Add}" />
</StackPanel>
</StackPanel>
Maybe the whole MVVM thing confuses me too much but I can't seem to find a solution for this.
I would be happy about any information on how to approach this correctly.

How to use communication between ViewModels in WPF Catel framework?

When I click to register button (for example) in RegistrationViewModel or any other ViewModel, I would like to change the LeftSlide and MainSlide properties in MainWindowViewModel
LeftSlide = _leftSlides[0];
MainSlide = any other ViewModel;
I've read this documentation, but i can't implement this in my code
https://catelproject.atlassian.net/wiki/display/CTL/MVVM+communication+styles
How can i do this? Help me please
MainWindow.xaml
<DockPanel LastChildFill="True">
<DockPanel DockPanel.Dock="Top">
<Grid Width="120" DockPanel.Dock="Left">
<ContentControl Content="{Binding LeftSlide,
Converter={StaticResource ViewModelToViewConverter}}"></ContentControl>
</Grid>
<Grid DockPanel.Dock="Right">
<ContentControl Content="{Binding MainSlide,
Converter={StaticResource ViewModelToViewConverter}}"></ContentControl>
</Grid>
</DockPanel>
</DockPanel>
RegistrationViewModel.cs
public class RegistrationViewModel : ViewModelBase
{
public RegistrationViewModel()
{
RegistrationUserCommand = new Command(OnRegistrationUserCommandExecute);
}
public Command RegistrationUserCommand { get; private set; }
private void OnRegistrationUserCommandExecute()
{
//when I click this button, I need to change LeftSlide property in
// MainWindowViewModel to _leftSlides[1] (LeftSlide = _leftSlides[1];)
//for example
}
}
MainWindowViewModel.cs
public class MainWindowViewModel : ViewModelBase
{
private List<IViewModel> _leftSlides;
private List<IViewModel> _mainSlides;
public MainWindowViewModel()
{
_leftSlides = new List<IViewModel>
{
new NavViewModel(),
new AnotherNavViewModel()
//another ViewModels
};
_mainSlides = new List<IViewModel>
{
new RegistrationViewModel()
//another ViewModels
};
//Default ViewModels
MainSlide = _mainSlides[0];
LeftSlide = _leftSlides[0];
}
public IViewModel LeftSlide { get; set; }
public IViewModel MainSlide { get; set; }
}
I haven't any experience with WPF and Catel. It's our first project with my team. We are studying. And we can't stop using Catel Framework.

Custom WPF TreeView

I have a custom class that I would like to bind a WPF TreeView that has three tiers. Each tier needs to be bound like this:
Monitor
--> LCD
--> Samsung 1445 LCD
--> CRT
--> Sony 125 CRT
Here is the example code:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
SystemInventory sysInventory = new SystemInventory();
//Possibly do something like this.
_myTreeView.DataContext = sysInventory.DeviceGroupInstances;
}
public class SystemInventory
{
public ObservableCollection<DeviceGroup> DeviceGroupInstances { get; set; }
public SystemInventory()
{
DeviceGroupInstances = new ObservableCollection<DeviceGroup>();
DeviceGroupInstances.Add(new DeviceGroup("Monitor"));
}
}
public class DeviceGroup
{
public string DeviceGroupName { get; set; }
public ObservableCollection<DeviceType> DeviceTypeInstances { get; set; }
public DeviceGroup(string deviceGroupName)
{
DeviceTypeInstances = new ObservableCollection<DeviceType>();
DeviceGroupName = deviceGroupName;
if (deviceGroupName == "Monitor")
{
DeviceTypeInstances.Add(new DeviceType("LCD"));
DeviceTypeInstances.Add(new DeviceType("CRT"));
}
}
}
public class DeviceType
{
public string DeviceTypeName { get; set; }
public ObservableCollection<DeviceInstance> DeviceInstances { get; set; }
public DeviceType(string deviceGroupName)
{
DeviceInstances = new ObservableCollection<DeviceInstance>();
DeviceTypeName = deviceGroupName;
if (deviceGroupName == "Monitor")
{
DeviceInstances.Add(new DeviceInstance("Samsung 1445 LCD"));
}
else
{
DeviceInstances.Add(new DeviceInstance("Sony 125 CRT"));
}
}
}
public class DeviceInstance
{
public string DeviceInstanceName { get; set; }
public DeviceInstance(string instanceName)
{
DeviceInstanceName = instanceName;
}
}
}
First FIX
Change CTOR:
public MainWindow()
{
InitializeComponent();
SystemInventory sysInventory = new SystemInventory();
//Possibly do something like this.
this.Content = sysInventory;
}
Second FIX
Use XAML below (add to MainWindow), where {x:Type pc:MainWindow+SystemInventory} used for nested classes
<Window.Resources>
<DataTemplate DataType="{x:Type pc:MainWindow+SystemInventory}">
<TreeView ItemsSource="{Binding DeviceGroupInstances}"/>
</DataTemplate>
<HierarchicalDataTemplate DataType="{x:Type pc:MainWindow+DeviceGroup}" ItemsSource="{Binding DeviceTypeInstances}">
<Label Content="{Binding DeviceGroupName}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type pc:MainWindow+DeviceType}" ItemsSource="{Binding DeviceInstances}">
<Label Content="{Binding DeviceTypeName}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type pc:MainWindow+DeviceInstance}">
<Label Content="{Binding DeviceInstanceName}"/>
</DataTemplate>
</Window.Resources>
About namespaces
Namespace is what you need to know to use classes. If your classes have namespace:
namespace MyCompany.MyProject.MyComponent
{
public class SystemInventory
{
....
}
}
This namespace should be added to XAML with alias to use:
<Window x:Class="MyCompany.MyProject.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:myAlias="clr-namespace:MyCompany.MyProject.MyComponent"
Title="Window1" Height="300" Width="350">
<Window.Resources>
...
Now you can use this classes in XAML like:
<DataTemplate DataType="{x:Type myAlias:DeviceInstance}">
<Label Content="{Binding DeviceInstanceName}"/>
</DataTemplate>
Just translate the property names in the answer Charlie already gave you.
<TreeView ItemsSource="{Binding DeviceGroups}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding DeviceTypeInstances}">
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding DeviceInstances}">
<TextBlock Text="{Binding DeviceInstanceName}"/>
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
<TextBlock Text="{Binding DeviceTypeName}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
You need to use a HierarchicalDataTemplate. There are many examples of this on Stack Overflow (and elsewhere) and if you search around you'll quickly find one. I would create a mock-up to illustrate it, but based on your very low accept rate, I'm not sure it would be worth it. You should look over your past questions and accept answers for all of them.

Categories