I am new with WPF - I want to create a tester for my server
I want to have on the right side of the application a TreeView and whenever a user selects a node - appropriate items is shown on the right side. For example I have a Connection node and under it many Sessions nodes, The Connection and Session have different parameters. I have built the tree view using mvvm and all works fine, but how can I achieve the second goal?
the xaml
<Window x:Class="Tree1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:self ="clr-namespace:Tree1"
xmlns:models ="clr-namespace:Tree1.Models"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<HierarchicalDataTemplate DataType="{x:Type self:TestPlanViewModel}" ItemsSource="{Binding Connections}">
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type self:ConnectionViewModel}" ItemsSource="{Binding Sessions}">
<StackPanel>
<TextBlock Text="Connection" Margin="10, 0, 0,0"></TextBlock>
</StackPanel>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type self:SessionViewModel}">
<StackPanel Orientation="Horizontal">
<TextBlock Margin="10,0,0,0" Text="Session"></TextBlock>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid Margin="10">
<Button Height="23" VerticalAlignment="Top" Margin="277,10,144,0" Name="addSessionBtn" Width="76"></Button>
<TreeView Name="testPlanTview" Margin="0,10,283,0" SelectedItemChanged="testPlanTview_SelectedItemChanged">
<TreeViewItem ItemsSource="{Binding Connections}" Header="Test Plan">
</TreeViewItem>
</TreeView>
</Grid>
You can use DataTemplates. There are two possible solution for your problem.
First of all let's create a base class:
public abstract class SelectableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool isSelected;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public bool IsSelected
{
get
{
return isSelected;
}
set
{
if (isSelected != value)
{
isSelected = value;
OnPropertyChanged("IsSelected");
}
}
}
}
For our example we have two classes:
Car, whose properties are "Hp" (int) and "Matriculation" (DateTime)
Person, whose properties are "Name" (string) and "Surname" (string)
Both of them extend SelectableObject.
Now the ViewModel (of course it is just a sample):
public class ViewModel : SelectableObject
{
private ArrayList tree = new ArrayList();
private ObjectWrapper current;
private Car car = new Car();
private Person person = new Person();
public ViewModel()
{
car.Hp = 120;
car.Matriculation = DateTime.Today;
car.PropertyChanged += new PropertyChangedEventHandler(OnItemPropertyChanged);
person.Name = "John";
person.Surname = "Doe";
person.PropertyChanged += new PropertyChangedEventHandler(OnItemPropertyChanged);
tree.Add(car);
tree.Add(person);
}
private void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
SelectableObject impl = (SelectableObject)sender;
if (e.PropertyName == "IsSelected" && impl.IsSelected)
{
Current = new ObjectWrapper(impl);
}
}
public ObjectWrapper Current
{
get
{
return current;
}
private set
{
current = value;
OnPropertyChanged("Current");
}
}
public IEnumerable Tree
{
get
{
return tree;
}
}
}
The ViewModel uses a the ObjectWrapper class:
public class ObjectWrapper
{
private readonly object wrappedInstance;
private readonly ReadOnlyCollection<PropertyWrapper> propertyWrappers;
public ObjectWrapper(object instance)
{
Collection<PropertyWrapper> collection = new Collection<PropertyWrapper>();
Type instanceType;
wrappedInstance = instance;
instanceType = instance.GetType();
foreach (PropertyInfo propertyInfo in instanceType.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance))
{
collection.Add(new PropertyWrapper(instance, propertyInfo));
}
propertyWrappers = new ReadOnlyCollection<PropertyWrapper>(collection);
}
public ReadOnlyCollection<PropertyWrapper> PropertyWrappers
{
get
{
return propertyWrappers;
}
}
public object Instance { get { return wrappedInstance; } }
}
public class PropertyWrapper
{
private readonly object instance;
private readonly PropertyInfo propertyInfo;
public PropertyWrapper(object instance, PropertyInfo propertyInfo)
{
this.instance = instance;
this.propertyInfo = propertyInfo;
}
public string Label
{
get
{
return propertyInfo.Name;
}
}
public Type PropertyType
{
get
{
return propertyInfo.PropertyType;
}
}
public object Value
{
get
{
return propertyInfo.GetValue(instance, null);
}
set
{
propertyInfo.SetValue(instance, value, null);
}
}
}
First solution (the easiest one)
You can use implicit datatemplating:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
xmlns:toolkit="http://schemas.xceed.com/wpf/xaml/toolkit"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="MainWindow" Height="350" Width="600">
<DockPanel>
<TreeView ItemsSource="{Binding Tree}" DockPanel.Dock="Left" Margin="5">
<TreeView.ItemContainerStyle>
<Style BasedOn="{StaticResource {x:Type TreeViewItem}}" TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
<ContentControl Content="{Binding Path=Current.Instance, Mode=OneWay}" Margin="5">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type local:Car}">
... define your car template here ...
</DataTemplate>
<DataTemplate DataType="{x:Type local:Person}">
... define your person template here ...
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</DockPanel>
</Window>
Second solution (imho the best one)
You can take advantage of ObjectWrapper object, by using an ItemsControl (here I used Extended WPF Toolkit for DateTime and Int controls):
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
xmlns:toolkit="http://schemas.xceed.com/wpf/xaml/toolkit"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="MainWindow" Height="350" Width="600">
<Window.Resources>
<local:ItemTemplateSelector x:Key="ItemTemplateSelector" />
<DataTemplate x:Key="{x:Type sys:String}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Label, Mode=OneWay}" VerticalAlignment="Center" />
<TextBox Text="{Binding Path=Value, Mode=TwoWay}" Margin="5" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="{x:Type sys:DateTime}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Label, Mode=OneWay}" VerticalAlignment="Center" />
<toolkit:DateTimePicker Value="{Binding Path=Value, Mode=TwoWay}" Margin="5" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="{x:Type sys:Int32}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Label, Mode=OneWay}" VerticalAlignment="Center" />
<toolkit:IntegerUpDown Value="{Binding Path=Value, Mode=TwoWay}" Margin="5" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<DockPanel>
<TreeView ItemsSource="{Binding Tree}" DockPanel.Dock="Left" Margin="5">
<TreeView.ItemContainerStyle>
<Style BasedOn="{StaticResource {x:Type TreeViewItem}}" TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
<ItemsControl ItemsSource="{Binding Path=Current.PropertyWrappers, Mode=OneWay}"
Margin="5" ItemTemplateSelector="{StaticResource ItemTemplateSelector}" />
</DockPanel>
</Window>
The implementation of the ItemTemplateSelector it is not difficult:
public class ItemTemplateSelector : DataTemplateSelector
{
public override System.Windows.DataTemplate SelectTemplate(object item, System.Windows.DependencyObject container)
{
PropertyWrapper propertyWrapper = (PropertyWrapper)item;
FrameworkElement frameworkElement = (FrameworkElement)container;
DataTemplate dataTemplate = (DataTemplate)frameworkElement.TryFindResource(propertyWrapper.PropertyType);
return dataTemplate;
}
}
My answer is quite long, but I wanted to show you both roads you can use.
Of course you can improve the PropertyWrapper by using attributes.
If you're already using MVVM i would sugest something like System.Windows.Interactivity and Prism regions to implement what you need.
For example:
Xaml:
<TreeView Name="testPlanTview" Margin="0,10,283,0">
<TreeViewItem ItemsSource="{Binding Connections}" Header="Test Plan">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectedItemChanged">
<i:InvokeCommandAction Command="{Binding OpenNewViewCommand}"
CommandParameter="{Binding SelectedItem,ElementName=testPlanTview}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TreeViewItem>
</TreeView>
<ContentControl prism:RegionManager.RegionName="MyRegion"/>
Viewmodel:
public ICommand OpenNewViewCommand
{
get { return this.selectedCommand; }
}
In the view model constructor you add:
this.selectedCommand = new DelegateCommand<YourModel>(this.SelectedExecute);
And the command:
private void SelectedExecute(YourModel parameter)
{
this.regionManager.RequestNavigate(RegionNames.MyRegion, new Uri("ViewToNavigate", UriKind.Relative), parameters);
}
Please be aware that this is just an example on how to make the navigation possible with prism. For more information on what i'm suggesting you can check the msdn link here
Related
My MainView contains a TabControl with an ItemTemplate and a ContentTemplate.
The TabControl's ItemsSource is bound to the Property ObservableCollection<TabViewModel> TabCollection in my MainViewModel.
TabViewModel:
namespace LuxUs.ViewModels
{
public class TabViewModel
{
public string Name { get; set; }
public object VM {get; set;}
public TabViewModel(string name)
{
Name = name;
}
public TabViewModel(string name, object vm)
{
Name = name;
VM = vm;
}
}
}
I want to create the tabs with their tab's header AND content dynamically from the MainViewModel like this...:
MainViewModel:
using System.Collections.ObjectModel;
namespace LuxUs.ViewModels
{
public class MainViewModel : ObservableObject, IPageViewModel
{
public ObservableCollection<TabViewModel> TabCollection { get; set; }
public MainViewModel()
{
TabCollection = new ObservableCollection<TabViewModel>();
TabCollection.Add(new TabViewModel("Dachdefinition", new DachdefinitionViewModel()));
TabCollection.Add(new TabViewModel("Baukörperdefinition"));
TabCollection.Add(new TabViewModel("Fassade"));
TabCollection.Add(new TabViewModel("Raumdefinition"));
TabCollection.Add(new TabViewModel("Treppenloch | Galerieöffnung"));
}
}
}
View:
<UserControl x:Class="LuxUs.Views.MainView"
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"
xmlns:local="clr-namespace:LuxUs.Views"
xmlns:models="clr-namespace:LuxUs.Models"
xmlns:vm="clr-namespace:LuxUs.ViewModels"
mc:Ignorable="d">
<Grid>
<Grid>
<TabControl Style="{DynamicResource TabControlStyle}" ItemsSource="{Binding TabCollection}" >
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<UserControl>
<ContentControl Content="{Binding VM}" />
</UserControl>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Grid>
</UserControl>
... but the content of each tab won't show. Instead, I get this text. The ViewModel ist the correct one but it should load the view instead of showing this text, of course:
The first tab's ViewModel DachdefinitionViewModel has only an empty constructor:
using System.Collections.ObjectModel;
namespace LuxUs.ViewModels
{
public sealed class DachdefinitionViewModel : ObservableObject
{
public DachdefinitionViewModel()
{
}
}
}
And here is its view Dachdefinition.xaml:
<UserControl x:Class="LuxUs.Views.Dachdefinition"
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"
xmlns:local="clr-namespace:LuxUs.Views"
xmlns:vm="clr-namespace:LuxUs.ViewModels"
mc:Ignorable="d">
<UserControl.DataContext>
<vm:DachdefinitionViewModel></vm:DachdefinitionViewModel>
</UserControl.DataContext>
<Grid Margin="50">
...
...
...
</Grid>
</UserControl>
Is the binding correct here or do I need to bind differently? Why is the view not showing up inside the first tab?
you need to connect view model with correct view via DataTemplate.
DataTemplate provides visual representation for data type which doesn't have it (your view model). If you don't specify DataTemplate, you will get default one: TextBlock with string representation of object (result of ToString() method, default to type name).
<Grid>
<Grid.Resources>
<DataTemplate DataType="{x:Type vm:DachdefinitionViewModel}">
<views:Dachdefinition />
</DataTemplate>
</Grid.Resources>
<TabControl Style="{DynamicResource TabControlStyle}"
ItemsSource="{Binding TabCollection}" >
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<UserControl>
<ContentControl Content="{Binding VM}" />
</UserControl>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
Your TabControl declaration should look like this:
<TabControl ItemsSource="{Binding TabCollection}">
<TabControl.Resources>
<DataTemplate DataType="{x:Type vm:DachdefinitionViewModel}">
<local:Dachdefinition/>
</DataTemplate>
</TabControl.Resources>
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding Name}"/>
<Setter Property="Content" Value="{Binding VM}"/>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
And the Dachdefinition UserControl must not set its own DataContext property, because the DataContext value is supposed to be inherit from the control's parent element, i.e. the TabItem.
<UserControl x:Class="LuxUs.Views.Dachdefinition" ...>
<!--
do not set UserControl.DataContext here
-->
<Grid Margin="50">
...
</Grid>
</UserControl>
Yes there is a problem in databinding.
at
<ContentControl Content="{Binding VM}" />
This line would just display ToString() value of the object bound to
it. (VM in this case).
Instead you can try using ContentTemplateSelection where you can choose the type of ContentTemplate in run time based on the type of object bound to it.
class TabContentTemplateSelector:DataTemplateSelector
{
public DataTemplate DefaultTemplate { get; set; }
public DataTemplate DachdeTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is TabViewModel tabViewModel)
{
if (tabViewModel.VM != null && tabViewModel.VM is DachdefinitionViewModel)
{
return DachdeTemplate;
}
else
{
return DefaultTemplate;
}
}
return base.SelectTemplate(item, container);
}
}
<DataTemplate x:Key="DachdeTemplate">
</DataTemplate>
<DataTemplate x:Key="SomeOtherTemplate">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
<local:TabContentTemplateSelector x:Key="myTabContentTemplateSelector"
DachdeTemplate="{StaticResource DachdeTemplate}"
DefaultTemplate="{StaticResource
SomeOtherTemplate}"
/>
</UserControl.Resources>
<Grid>
<TabControl ItemsSource="{Binding TabCollection}"
ContentTemplateSelector="{StaticResource
myTabContentTemplateSelector}" >
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
</Grid>
https://www.c-sharpcorner.com/UploadFile/41e70f/dynamically-selecting-datatemplate-for-wpf-listview-way-1/
Check this for how to dynamically change template.. In the template you can use any kind of user control.
I am totally new to this language, so I feel it is a bit silly question ..
I am trying to create a hamburger menu with the MahApps.Metro framework through an example from his example repository, the V4 versión with MVVM.
Copying all the structure of his example, it works correctly, but now I don't know how to continue. I don't know how to get the properties of my MainViewModel from my SearchViewModel, because when accessing the object I don't see its properties.
Also, I don't know how to bind these properties or how to link a button in the SearchView.xaml with an action that uses MainViewModel properties.
This are all my relevant classes, but you can follow the example from his example repo
MainWindow.xaml
<Window.DataContext>
<viewModels:MainViewModel />
</Window.DataContext>
<Grid.Resources>
<core:SelectedItemToContentConverter x:Key="SelectedItemToContentConverter" />
<!-- this is the template for the items (options too) -->
<DataTemplate x:Key="MenuItemTemplate" DataType="{x:Type Controls:HamburgerMenuIconItem}">
<Grid x:Name="RootGrid" Height="48" Background="Transparent">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="48" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ContentControl Grid.Column="0" HorizontalAlignment="Center" VerticalAlignment="Center" Content="{Binding Icon}" Focusable="False" />
<TextBlock Grid.Column="1" VerticalAlignment="Center" FontSize="16" Text="{Binding Label}" />
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Controls:HamburgerMenu}}, Path=IsPaneOpen}" Value="False">
<Setter TargetName="RootGrid" Property="ToolTip" Value="{Binding ToolTip, Mode=OneWay}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
<!-- these are the templates for the view models -->
<DataTemplate DataType="{x:Type viewModels:HomeViewModel}">
<views:HomeView DataContext="{Binding}" />
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:PrivateViewModel}">
<views:PrivateView DataContext="{Binding}" />
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:SettingsViewModel}">
<views:SettingsView DataContext="{Binding}" />
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:AboutViewModel}">
<views:AboutView DataContext="{Binding}" />
</DataTemplate>
<Controls:HamburgerMenu Grid.Row="1" x:Name="HamburgerMenuControl" HamburgerVisibility="Hidden" HamburgerWidth="48" ItemTemplate="{StaticResource MenuItemTemplate}" ItemsSource="{Binding MenuItems}" IsPaneOpen="True" SelectedIndex="0" Style="{StaticResource HamburgerMenuCreatorsStyle}" OpenPaneLength="350" VerticalScrollBarOnLeftSide="False" Grid.RowSpan="3" Grid.ColumnSpan="2">
<!-- select the tag (ViewModel) of the selected item (options item) -->
<Controls:HamburgerMenu.Content>
<MultiBinding Converter="{StaticResource SelectedItemToContentConverter}">
<Binding FallbackValue="{x:Null}" Mode="OneWay" Path="SelectedItem.Tag" RelativeSource="{RelativeSource Self}" />
<Binding FallbackValue="{x:Null}" Mode="OneWay" Path="SelectedOptionsItem.Tag" RelativeSource="{RelativeSource Self}" />
</MultiBinding>
</Controls:HamburgerMenu.Content>
</Controls:HamburgerMenu>
</Grid.Resources>
PropertyChangedViewModel base model
using System.ComponentModel;
using System.Runtime.CompilerServices;
using JetBrains.Annotations;
namespace ExampleProject.ViewModels
{
public class PropertyChangedViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
MainViewModel
namespace ExampleProject.ViewModels
{
public class MainViewModel : PropertyChangedViewModel
{
private HamburgerMenuItemCollection _menuItems;
private HamburgerMenuItemCollection _menuOptionItems;
public MainViewModel()
{
this.CreateMenuItems();
}
public void CreateMenuItems()
{
MenuItems = new HamburgerMenuItemCollection
{
new HamburgerMenuIconItem()
{
Icon = new PackIconMaterial() {Kind = PackIconMaterialKind.Magnify},
Label = "Search Item",
ToolTip = "The Search view.",
Tag = new SearchViewModel(this)
},
new HamburgerMenuIconItem()
{
Icon = new PackIconMaterial() {Kind = PackIconMaterialKind.CreditCardOutline},
Label = "Payment item",
ToolTip = "Payment.",
Tag = new PaymentViewModel(this)
},
new HamburgerMenuIconItem()
{
Icon = new PackIconMaterial() {Kind = PackIconMaterialKind.BookmarkMultipleOutline},
Label = "The Bookmarks",
ToolTip = "The bookmarks item.",
Tag = new BookmarksViewModel(this)
}
};
MenuOptionItems = new HamburgerMenuItemCollection
{
new HamburgerMenuIconItem()
{
Icon = new PackIconMaterial() {Kind = PackIconMaterialKind.Help},
Label = "About",
ToolTip = "Some help.",
Tag = new AboutViewModel(this)
}
};
}
public HamburgerMenuItemCollection MenuItems
{
get { return _menuItems; }
set
{
if (Equals(value, _menuItems)) return;
_menuItems = value;
OnPropertyChanged();
}
}
public HamburgerMenuItemCollection MenuOptionItems
{
get { return _menuOptionItems; }
set
{
if (Equals(value, _menuOptionItems)) return;
_menuOptionItems = value;
OnPropertyChanged();
}
}
}
}
SearchViewModel
namespace ExampleProject.ViewModels
{
public class SearchViewModel : PropertyChangedViewModel
{
private readonly PropertyChangedViewModel _mainViewModel;
public SearchViewModel(PropertyChangedViewModel mainViewModel)
{
_mainViewModel = mainViewModel;
}
}
}
SearchView.xaml
<UserControl x:Class="ExampleProject.Views.SearchView"
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"
xmlns:local="clr-namespace:ExampleProject.Views"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<TextBlock Text="Search View"
FontSize="32"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Grid>
</UserControl>
SearchView.xaml (code behind)
namespace ExampleProject.Views
{
/// <summary>
/// Logic of interaction for SearchtView.xaml
/// </summary>
public partial class SearchView : UserControl
{
public SearchView()
{
InitializeComponent();
}
}
}
Thank you.
PropertyChangedViewModel does not have myGlobalVar, MainViewModel has.
you have to cast mainViewModel to MainViewModel because mainViewModel is of type PropertyChangedViewModel and not MainViewModel.
((MainViewModel)mainViewModel).myGlobalVar = ...
This is why respecting the "naming conventions" is very important, hadn't you named it after the wrong class, you wouldn't have confused it with another class.
Also in order for the binding to work, the class member must be a public property or else it is not accessible from XAML and it won't show up in XAML intellisense.
If you don't want it to be public you must use DependencyProperty instead.
I have two problems, one small and one big, and I need help;
First, this is my code :
<ComboBox Name="cmb1" Width="165" Height="25" Margin="25,5,10,10"
ItemsSource="{Binding ChoicesList}"
SelectedItem="{Binding SelectedChoiceList}">
</ComboBox>
<ComboBox Name="cmb2" Width="165" Height="25" Margin="10,5,25,10">
<ComboBox.Style>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding Sections}"></Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedChoiceList}" Value="Contract">
<Setter Property="ItemsSource" Value="{Binding Contract}"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding SelectedChoiceList}" Value="Service">
<Setter Property="ItemsSource" Value="{Binding Services}"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
Text="{Binding Name}"
Margin="4,0"/>
<TextBlock Grid.Column="0"
Text="{Binding label}"
Margin="0"/>
<TextBlock Grid.Column="0"
Text="{Binding Start, StringFormat=d}"
Margin="0"/>
<TextBlock Grid.Column="1"
Text="{Binding End,StringFormat=d}"
Margin="0"/>
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Another day, another problem!
In cmb2, I would like nothing to be loaded until I have made one of the three choices, is there a way?
(for the moment I load "section" list if nothing is chosen, and the binding changes according to the choices. I made this choice because the value of "section" can change, it can be "A" or "B" or "C" and more, so I would like "service" = service list, "contract" = contract list and "section"= all other values)
More importantly, I would like to rework this code because currently the DataTemplate is the same for all three binds, which was not awkward (If I choose "section", textblock displays "name" selected, cause section have no label, for example) but I would like, for contracts, to display "name" + "from" + "Starting date" + "To" + "Ending date".
If I add textblock "text = from" and "text = to", it will apply to all my choices, and I would have, for example, if I choose "section", a result like "nameOfSection + from + to", and I don't want that.
So I would like the textblock "from" and "to" to appear only for the choice "contract", I don't know if it's possible?
Hoping to have been clear, and thanking you for your time for the help you will give me;
Welcome to SO!
To answer your first point by removing the line that is setting the ItemSource you should be able to have nothing loaded
<Setter Property="ItemsSource" Value="{Binding Sections}"></Setter> can be removed and replaced with another DataTrigger.
To answer your second point you can use DataTemplates with the DataType property to apply a style to a specific data type. This means you can setup each of your, Service, Section and Contract to look differently / template their own properties.
<DataTemplate DataType="{x:Type classes:Contract}">
<Label Content="Contract"/>
</DataTemplate>
<DataTemplate DataType="{x:Type classes:Service}">
<Label Content="Service"/>
</DataTemplate>
<DataTemplate DataType="{x:Type classes:Section}">
<Label Content="Section"/>
</DataTemplate>
This bit of code demonstrates that.
Here is also a sample application I have made that should do what you've asked.
View
<Window x:Class="SOHelp.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:classes="clr-namespace:SOHelp.Classes"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<DataTemplate DataType="{x:Type classes:Contract}">
<Label Content="Contract"/>
</DataTemplate>
<DataTemplate DataType="{x:Type classes:Service}">
<Label Content="Service"/>
</DataTemplate>
<DataTemplate DataType="{x:Type classes:Section}">
<Label Content="Section"/>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<ComboBox Grid.Row="0" Width="165" Height="25"
ItemsSource="{Binding ChoicesList}"
DisplayMemberPath="Name"
SelectedItem="{Binding SelectedChoiceList}"/>
<ComboBox Grid.Row="1" Width="165" Height="25"
ItemsSource="{Binding SelectedChoiceList.Items}"/>
</Grid>
</Window>
Code behind / could be ViewModel
public partial class MainWindow : Window, INotifyPropertyChanged
{
private ObservableCollection<Option> _choicesList;
private Option _selectedChoiceList;
public Option SelectedChoiceList
{
get { return _selectedChoiceList; }
set { _selectedChoiceList = value; NotifyPropertyChanged(); }
}
public ObservableCollection<Option> ChoicesList
{
get { return _choicesList; }
set { _choicesList = value; NotifyPropertyChanged(); }
}
public MainWindow()
{
InitializeComponent();
ChoicesList = new ObservableCollection<Option>
{
new Option("Contract", new ObservableCollection<object>
{
new Contract(), new Contract(), new Contract()
}),
new Option("Service", new ObservableCollection<object>
{
new Service(), new Service(), new Service()
}),
new Option("Section", new ObservableCollection<object>
{
new Section(), new Section(), new Section()
}),
};
DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Classes I created
public class Option
{
public string Name { get; set; }
public ObservableCollection<object> Items { get; set; }
public Option(string name, ObservableCollection<object> items)
{
Name = name;
Items = items;
}
}
public class Service
{
}
public class Section
{
}
public class Contract
{
}
Hope some of this helps. Let me know if you have any questions.
I have an ItemsControl, and a Button outside the ItemsControl. Each item inside the ItemsControl has a dependency property called "MyProperty" (defined in the code-behind).
I would like to set the IsEnabled property of the Button to false when at least one of the items in the ItemsControl has the MyProperty property set to 5. (of course this is just a stupid example of a more complicated situation)
I tried by means of a data trigger, but with no luck:
XAML:
<Window x:Class="cancellami24.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Style x:Key="MyStyle" TargetType="{x:Type ContentPresenter}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=MyProperty}" Value="5">
<Setter Property="IsEnabled" TargetName="MyButton" Value="False" /><!--error on TargetName-->
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<ItemsControl x:Name="MyListBox" Grid.Row="0" ItemContainerStyle="{StaticResource MyStyle}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=MyProperty, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button x:Name="MyButton" Grid.Row="1" Click="MyButton_Click"/>
</Grid>
</Window>
Code-behind:
using System.Collections.ObjectModel;
using System.Windows;
namespace cancellami24
{
public partial class MainWindow : Window
{
private readonly ObservableCollection<MyItem> myCollection = new ObservableCollection<MyItem>();
public MainWindow()
{
InitializeComponent();
myCollection.Add(new MyItem(1));
myCollection.Add(new MyItem(2));
myCollection.Add(new MyItem(3));
MyListBox.ItemsSource = myCollection;
}
private void MyButton_Click(object sender, RoutedEventArgs e)
{
myCollection[2].SetValue(MyItem.MyPropertyProperty, 5);
}
}
public class MyItem : DependencyObject
{
public static readonly DependencyProperty MyPropertyProperty = DependencyProperty.Register("MyProperty", typeof(int), typeof(MyItem));
public MyItem(int propertyValue)
{
SetValue(MyPropertyProperty, propertyValue);
}
}
}
You need custom converter to solve it
public class MyConverter : IValueConverter
{
bool flag = false;
var collection = value as ObservableCollection<MyItem>();
if(collection==null) return flag;
foreach (var item in collection)
{
if (item.MyProperty==5)
{
flag = true;
break;
}
}
return flag;
}
Add MyConverter to your App.xaml
<local:MyConverter x:key="MyConverter"/>
Xaml:
<Button x:Name="MyButton" IsEnabled="{Binding ElementName=MyListBox, Path=ItemsSource, Converter={StaticResource MyConverter}}"/>
I'm trying to use Caliburn.Micro to bind a view model of a nested ListBox but I'm stuck.
I have a list workspace that is divided in two sections a list of groups containtaining items in a different part of that workspace I want to show the details of either a group or an item in a group depending on what is the SelectedItem. I'm new to Caliburn.Micro and looked at the documentation and samples but don't know how to connect the dots. Specifically I'm trying to model this after the Caliburn.Micro.HelloScreens sample. The code I have so far:
The ViewModel:
public class AnalyzerGroupWorkspaceViewModel : Conductor<AnalyzerGroupWorkspaceViewModel>, IWorkspace
{
private Selected selected = Selected.AnalyzerGroup;
private const string name = "Analyzers";
public AnalyzerGroupWorkspaceViewModel(
IMappingEngine fromMapper,
IRepository<Model.AnalyzerGroup> analyzerGroups)
{
AnalyzerGroups = new ObservableCollection<IAnalyzerGroupViewModel>(analyzerGroups.GetAll().Select(fromMapper.Map<Model.AnalyzerGroup,AnalyzerGroupViewModel>));
}
public ObservableCollection<IAnalyzerGroupViewModel> AnalyzerGroups { get; private set; }
public string Name { get { return name; } }
public Selected Selected
{
get { return selected; }
set
{
if (value == selected) return;
selected = value;
NotifyOfPropertyChange(() => Selected);
}
}
private IConductor Conductor { get { return (IConductor) Parent; } }
public void Show()
{
var haveActive = Parent as IHaveActiveItem;
if (haveActive != null && haveActive.ActiveItem == this)
{
DisplayName = name;
Selected = Selected.AnalyzerGroup;
}
else
{
Conductor.ActivateItem(this);
}
}
}
The view:
<UserControl x:Class="Philips.HHDx.SSW.AnalyzerGroup.AnalyzerGroupWorkspaceView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cal="http://www.caliburnproject.org">
<DockPanel>
<GroupBox Header="AnalyzerGroups" DockPanel.Dock="Top">
<ListBox x:Name="AnalyzerGroups">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Name}" />
<ListBox x:Name="Analyzers">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Id }"></TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</GroupBox>
<GroupBox Header="Details">
<ContentControl cal:View.Context="{Binding Selected, Mode=TwoWay}"
cal:View.Model="{Binding}"
VerticalContentAlignment="Stretch"
HorizontalContentAlignment="Stretch"/>
</GroupBox>
</DockPanel>
</UserControl>
Next to that I have two UserControls that display the detailsof a group or item.
My specific question is how can I use the SelectedItem property of the two ListBoxes to modify the Selected property to switch between displaying the AnalyzerGroup details and the Analyzer details?
I've found the solution to the above described problem the solution consists of four parts:
Add a IsSelected property (that notifies changes) to both 'child' ViewModels
Bind the IsSelected property of the ListBox.ItemContainerStyle to the IsSelected property of the respective ViewModels
Attach a Caliburn.Micro Message to the 'outer' ListBox and use the $eventArgs argument
In the ViewModel bound to the entire UserControl implement the method corresponding to the Message and use the AddedItems property of the eventArgs to set the SelectedViewModel property setting the IsSelected property of the previous SelectedViewModel to false
The code then becomes:
The ViewModel:
public class AnalyzerGroupWorkspaceViewModel : PropertyChangedBase, IAnalyzerGroupWorkspaceViewModel
{
private IAnalyzerViewModel selectedViewModel;
private const string WorkspaceName = "Analyzers";
public AnalyzerGroupWorkspaceViewModel(
IMappingEngine fromMapper,
IRepository<Model.AnalyzerGroup> analyzerGroups)
{
AnalyzerGroups = new ObservableCollection<IAnalyzerGroupViewModel>(
analyzerGroups.GetAll().Select(
fromMapper.Map<Model.AnalyzerGroup, AnalyzerGroupViewModel>));
}
public void SelectionChanged(object eventArgs)
{
var typedEventArgs = eventArgs as SelectionChangedEventArgs;
if (typedEventArgs != null)
{
if (typedEventArgs.AddedItems.Count > 0)
{
var item = typedEventArgs.AddedItems[0];
var itemAsGroup = item as IAnalyzerViewModel;
if (itemAsGroup != null)
{
SelectedViewModel = itemAsGroup;
}
}
}
}
public ObservableCollection<IAnalyzerGroupViewModel> AnalyzerGroups { get; private set; }
public string Name { get { return WorkspaceName; } }
public IAnalyzerViewModel SelectedViewModel
{
get { return selectedViewModel; }
set
{
if (Equals(value, selectedViewModel))
{
return;
}
if (SelectedViewModel != null)
{
SelectedViewModel.IsSelected = false;
}
selectedViewModel = value;
NotifyOfPropertyChange(() => SelectedViewModel);
}
}
}
The View:
<UserControl x:Class="Philips.HHDx.SSW.AnalyzerGroup.AnalyzerGroupWorkspaceView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cal="http://www.caliburnproject.org">
<DockPanel>
<GroupBox Header="AnalyzerGroups" DockPanel.Dock="Top">
<ListBox SelectionMode="Single"
x:Name="AnalyzerGroups"
cal:Message.Attach="[Event SelectionChanged] = [Action SelectionChanged($eventArgs)]">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderThickness="2" BorderBrush="DarkGray">
<StackPanel Orientation="Vertical" Margin="10">
<TextBlock Text="{Binding Name}" />
<ListBox SelectionMode="Single" ItemsSource="{Binding Analyzers}" >
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"></StackPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderThickness="1" BorderBrush="DarkGray">
<StackPanel>
<TextBlock Text="{Binding Name }" Margin="10" />
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</GroupBox>
<GroupBox Header="Details">
<ContentControl cal:View.Model="{Binding SelectedViewModel}" />
</GroupBox>
</DockPanel>
</UserControl>
The answer to your specific question is yes, you can.
On the ViewModel of your UserControl. You create a property that is a ViewModel of either of the two details.
public interface IAnalyzerViewModel
{
}
Next, create two ViewModels for the Views of your Analyzer and AnalyzerGroup views.
public class AnalyzerGroupViewModel : IAnalyzerViewModel
{
}
public class AnalyzerViewModel : IAnalyzerViewModel
{
}
Next, create a property in your UserControl's ViewModel that implements INPC or PropertyChangedBase of Caliburn Micro.
public class MainViewModel :
{
private IAnalyzerViewModel _analyzerViewModel;
public IAnalyzerViewModel SelectedViewModel { get { return _analyzerViewModel; } set { _analyzerViewModel = value; OnPropertyChanged(() => SelectedViewModel); }
//Hook up the selected item changed event of your listbox and set the appropriate ViewModel to show, so if you either want to show the AnalyzerGroup or AnalyzerView.
}
And lastly, just update your MainView to
<ContentControl x:Name="SelectedViewModel"
VerticalContentAlignment="Stretch"
HorizontalContentAlignment="Stretch"/>
Caliburn will hook up the appropriate bindings and stuff and will pull the View for the associated ViewModel, and also the Name convention part will automatically map it to any public property of its datacontext as long as the names match.