I have this code snipper
DockPanel Margin="3"
HorizontalAlignment="Stretch" >
<Label
Content="Port #1"
DockPanel.Dock="Left"/>
<ComboBox
x:Name="Port1"
ItemsSource="{Binding PortList}"
SelectedItem="{Binding Port1Selected}">
</ComboBox>
</DockPanel>
<DockPanel Margin="3"
HorizontalAlignment="Stretch" >
<Label
Content="Port #2"
DockPanel.Dock="Left"/>
<ComboBox
ItemsSource="{Binding PortList}"
SelectedItem="{Binding Port2Selected}"/>
</DockPanel>
as you can see there are two DockPanels which are basically the same besides their SelectedItem property.
at the moment I have 8 those which I duplicated, but it seems like too much copy and paste and prone to errors, and in the future, I might have more port, so I will need to create a new DockPanel.
or even have more complex control that I will need to "duplicate".
I was trying to make custom control contains <DockPanel> /// </DockPanel > part, but i don't know to achieve the same functionality and having the same bindings
my question is, how can i achieve this without reputations.
i was looking here and here prism-example for some ideas
UPDATE
My item controller
<ItemsControl ItemsSource="{Binding PortList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<DockPanel HorizontalAlignment="Stretch"
Margin="3">
<Label
Content="{Binding PortName}"
DockPanel.Dock="Left"/>
<ComboBox
ItemsSource="{Binding PortList}"
SelectedItem="{Binding SelectedPort}"/>
</DockPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
my port model
public class PortModel : BindableBase
{
public string PortName { get; set; }
public List<string> PortList { get; set; }
// public string SelectedPort { get; set; }
private string _selectedPort;
public string SelectedPort
{
get { return _selectedPort; }
set { SetProperty(ref _selectedPort, value); }
}
}
and my main list in in the view model
#region List Properties
private ObservableCollection<PortModel> portsList;
public ObservableCollection<PortModel> PortList
{
get { return portsList; }
set { SetProperty(ref portsList, value); }
}
and adding items to the list
private void LoadSerialPorts()
{
portsList.Clear();
for (int i = 0; i < Constants.NUMBER_OF_COMS; i++)
{
PortList.Add(new PortModel
{
PortList = serialPortHandler.GetSerialPortPort().ToList(),
PortName = $"Port #{i}",
});
}
}
But now the selected port does not fire
Related
I have a multi tabbel interface where I use a number of ComboBoxes for defining a mapping between two objects. I am working in a MVVM design pattern. Since I do not know the number of mappings I need to create I used an ItemsControl with a DataTemplate to create the required GUI elements. Things seem to work nicely. The Mapping property is correctly setup.
But when the user switches to the 2nd tab and switches back to Tab 1, the data in the mapping structure is incorrect. The be precise: The Actual field is null. The Comboboxes reflect this by being empty.
The combobox that is not part of the ItemsControl, which uses the independent Mapping1 and Things2 properties, does not show this behavior. I am clearly missing something here, but I cannot figure it out.
My data models:
namespace DisconnectedItemTest.Models
{
public class Thing
{
public int Id { get; init; }
public string? Name { get; init; }
public object? TheRest { get; init; }
}
}
namespace DisconnectedItemTest.Models
{
public class Mapping
{
public string? Original { get; set; }
public Thing? Actual { get; set; }
}
}
namespace DisconnectedItemTest.Models
{
public class Mapping1
{
public string? Original { get; set; }
public Thing? Actual { get; set; }
}
}
My ViewModel
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using DisconnectedItemTest.Models;
namespace DisconnectedItemTest.ViewModels
{
// ReSharper disable once ClassNeverInstantiated.Global
public partial class Tab1ViewModel : ObservableObject, ITab
{
public string TabName { get; set; } = "Tab 1";
[ObservableProperty] private bool _isEnabled = true;
[ObservableProperty] private ObservableCollection<Mapping> _mapping;
[ObservableProperty] private ObservableCollection<Thing> _things;
[ObservableProperty] private ObservableCollection<Mapping1> _mapping1;
public Tab1ViewModel()
{
_things = new ObservableCollection<Thing>();
for (var i = 1; i <= 5; i++)
{
var thing = new Thing()
{
Id = i,
Name = $"Thing {i}",
TheRest = null
};
_things.Add(thing);
}
_mapping = new ObservableCollection<Mapping>();
_mapping1 = new ObservableCollection<Mapping1>();
for (var i = 1; i <= 2; i++)
{
var mapping = new Mapping()
{
Original = $"Original {i}",
Actual = null
};
_mapping.Add(mapping);
}
_mapping1 = new ObservableCollection<Mapping1>();
for (var i = 1; i <= 2; i++)
{
var mapping1 = new Mapping1()
{
Original = $"Original {i}",
Actual = null
};
_mapping1.Add(mapping1);
}
}
[RelayCommand]
void TriggerBreak()
{
}
}
}
My XAML for the first Tab
<UserControl x:Class="DisconnectedItemTest.Views.Tab1"
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:viewModels="clr-namespace:DisconnectedItemTest.ViewModels"
xmlns:models="clr-namespace:DisconnectedItemTest.Models"
xmlns:views="clr-namespace:DisconnectedItemTest.Views"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=viewModels:Tab1ViewModel}"
d:DesignHeight="450" d:DesignWidth="800" d:Background="White">
<UserControl.Resources>
<DataTemplate DataType="{x:Type models:Mapping}">
<StackPanel Orientation="Horizontal" Margin="0 0 0 5">
<TextBlock Text="{Binding Original}" MinWidth="125"></TextBlock>
<ComboBox ItemsSource="{Binding DataContext.Things, RelativeSource={RelativeSource FindAncestor, AncestorLevel=1, AncestorType={x:Type views:Tab1}}}"
SelectedItem="{Binding Actual, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="Name"
MinWidth="225"/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<GroupBox Grid.Row="0" Header="Comboboxes in ItemsControl" Margin="10" MinHeight="80">
<ItemsControl ItemsSource="{Binding Mapping}" Margin="10 10 10 5"/>
</GroupBox>
<GroupBox Grid.Row="1" Header="2 seperate ComboBoxes" Margin="10" MinHeight="80">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal" Margin="0 0 0 5">
<TextBlock Text="{Binding Mapping1[0].Original}" MinWidth="125"></TextBlock>
<ComboBox ItemsSource="{Binding Things}"
SelectedItem="{Binding Mapping1[0].Actual, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="Name"
MinWidth="225"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0 0 0 5">
<TextBlock Text="{Binding Mapping1[1].Original}" MinWidth="125"></TextBlock>
<ComboBox ItemsSource="{Binding Things}"
SelectedItem="{Binding Mapping1[1].Actual, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="Name"
MinWidth="225"/>
</StackPanel>
</StackPanel>
</GroupBox>
<Button Grid.Row="2" Width="75" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="10"
Command="{Binding TriggerBreakCommand}">BREAK</Button>
</Grid>
</UserControl>
I used two independent Mapping objects to prevent DataTemplate from ping up the data for the ComboBoxes not in the ItemsControl.
At first I tried to put the Comboboxes in a DataGrid, but after struggling to get the databinding to work correctly, I ran into the same issue as I do now.
So here I have a MVVM form. the Form contains a Datagrid which is connected to the Databank. I also have a ComboBox which I want to use as a filter option. The Filter option shoud filter by the "AnlV nr" so when the user selects "01" from the ComboBox the datagrid should refresh and show only that "AnlV nr" that have "01" Below I will share you the code and you can see that i've gotten as far as showing the "AnlV" values in the ComboBox but I now do not know how to do the rest and make the filter work. Below is my Viewmodel and the Xaml code.
If anyone can help me with this I would really apreciate it.
Xaml Code:
<Window x:Class="QBondsFrontend.Views.Input.AnlVTexteView"
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:QBondsFrontend.Views.Input" xmlns:input="clr-namespace:QBondsFrontend.ViewModels.Input" d:DataContext="{d:DesignInstance Type=input:AnlVTexteViewModel}"
mc:Ignorable="d"
Title="AnlVTexteView"
Width="800"
MinHeight="400"
Height="490"
MinWidth="1010"
MaxWidth="1010"
UseLayoutRounding="True">
<Grid Background="#A8A8A8" >
<StackPanel VerticalAlignment="Top" Background="#A8A8A8" Orientation="Horizontal" Height="57">
<Label
Content="AnlV Nr.:" Height="35" FontSize="12"/>
<ComboBox Background="LightGray" Height="20" Width="70" ItemsSource="{Binding lstAnlVTexte}" SelectedItem="{Binding Search}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding AnlVPara}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Button Height="18" Width="68" Margin="5, 0"
Content="Filter löschen" FontSize="11" Style="{StaticResource STL_ButtonStandard}"
x:Name="filterlöschen"
Command="{Binding icdFilterDelete}"/>
</StackPanel>
<StackPanel Background="LightGray" VerticalAlignment="Top" Height="177" Margin="0,57,0,0">
<DataGrid x:Name="datagridXAML"
Height="177"
ItemsSource="{Binding lstAnlVTexte, Mode=TwoWay}"
Style="{StaticResource STL_DataGridReporting}"
CellStyle="{StaticResource STL_DataGridCellReporting}"
ColumnHeaderStyle="{StaticResource STL_DataGridColumnHeaderReporting}"
AlternatingRowBackground="#A8A8A8"
CanUserResizeColumns="False"
>
<DataGrid.Columns>
<DataGridTextColumn Header="AnlV-Nr"
Binding="{Binding AnlVPara}"
Width="60"/>
<DataGridTextColumn Header="gültig ab"
Binding="{Binding TextGueltigAb}"
Width="68"/>
<DataGridTextColumn Header="Text"
Binding="{Binding ParaText}"
Width="750"/>
<DataGridTextColumn Header="Info"
Binding="{Binding Info}"
Width="*"/>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
<StackPanel Background="#A8A8A8" HorizontalAlignment="Center" Margin="10,268,0,141" Width="1010" >
<Label Content="Bearbeitungsbereich" FontWeight="Bold" FontSize="12" Height="33" />
</StackPanel>
<StackPanel>
<StackPanel Orientation="Horizontal" Background="#A8A8A8" HorizontalAlignment="Center"
Width="1010" Margin="0,294,0,0" Height="31">
<Label Height="25" Width="60" Margin="20, 0, 0, 0" Content="AnlV-Nr.:" />
<ComboBox IsEditable="True" Background="gray" Height="22" Width="69" ItemsSource="{Binding AnlVPara}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding lstAnlVTexte}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<CheckBox Height="15" Margin="10, 0, 0, 0" />
<Label Height="26" Width="122" Content="Editierwarnungen" />
<StackPanel Height="48" Width="100"/>
</StackPanel>
<StackPanel Height="22" Orientation="Horizontal">
<Label Margin="20, 0, 0, 0" Content="gültig ab:" Height="27" />
<TextBox Background="LightGray" Height="20" Width="100" />
</StackPanel>
<StackPanel Height="50" Orientation="Horizontal">
<Label Content="Text:" Height="27" Width="38" Margin="42,0,0,10" />
<TextBox Background="LightGray" Width="500" Height="43" />
</StackPanel>
<StackPanel Orientation="Horizontal" >
<Label Content="Info:" Height="27" Width="38" Margin="42,0,0,0" />
<TextBox Background="LightGray" Width="500" Height="20" />
<Button x:Name="BTN_speichern" Width="80" Height="18" Margin="20,0,0,0" Content="Speichern"
Style="{StaticResource STL_ButtonStandard}" Command="{Binding icdSpeichern}"/>
</StackPanel>
</StackPanel>
</Grid>
</Window>
ViewModel:
using Newtonsoft.Json;
using QBondsData.DBModels;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using System.Linq;
namespace QBondsFrontend.ViewModels.Input
{
public class AnlVTexteViewModel : BaseViewModel
{
#region data
private string _AnlVPara;
private DateTime _TextGueltigAb;
private string _ParaText;
private string _Info;
private List<AnlVhistText> _lstAnlVTexte;
private string _search;
#endregion
#region constructor
public AnlVTexteViewModel()
{
icdFilterDelete = new RelayCommand<object>(parameter => filterdelete(), parameter => true);
icdSpeichern = new RelayCommand<object>(parameter => speichern(), parameter => true);
GetData();
}
#endregion
#region members
public ICommand icdFilterDelete { get; set; }
public ICommand icdSpeichern { get; set; }
private string Search
{
get { return _search; }
set
{
_search = value;
OnPropertyChanged("Search");
}
}
public string AnlVPara
{
get
{
return _AnlVPara;
}
set
{
_AnlVPara = value;
OnPropertyChanged("AnlVPara");
}
}
public DateTime TextGueltigAb
{
get
{
return _TextGueltigAb;
}
set
{
_TextGueltigAb = value;
OnPropertyChanged("TextGueltigAb");
}
}
public string ParaText
{
get
{
return _ParaText;
}
set
{
_ParaText = value;
OnPropertyChanged("ParaText");
}
}
public string Info
{
get
{
return _Info;
}
set
{
_Info = value;
OnPropertyChanged("Info");
}
}
public List<AnlVhistText> lstAnlVTexte
{
get { return _lstAnlVTexte; }
set
{
_lstAnlVTexte = value;
OnPropertyChanged("lstAnlVTexte");
}
}
#endregion
#region methods
private void filterdelete()
{
}
private void speichern()
{
}
private async Task GetData()
{
HttpResponseMessage Response = await Globals.SendRequest("AnlVTexte/GetAnlVTexte"
, "GET");
if (Response.IsSuccessStatusCode)
{
lstAnlVTexte = JsonConvert.DeserializeObject<List<AnlVhistText>>
(await Response.Content.ReadAsStringAsync());
}
else
{
lstAnlVTexte = new List<AnlVhistText>();
Application.Current.Dispatcher.Invoke((Action)delegate
{
Globals.CloseReportByHash(this.GetHashCode()
, "Fehler! (HTTP Status " + Response.StatusCode + ")." +
"Kontaktieren Sie den Support.");
});
}
}
#endregion
}
}
When you change the type of lstAnlVTexte to ICollectionView you get two events CurrentChanged and CurrentChanging which you can handle in your viewmodel. From the ICollectionView you can get the CurrentItem.
Like this:
private List<AnlVhistText> _anlVTexte = new List<AnlVhistText>();
public AnlVTexteViewModel()
{
[...]
lstAnlVTexte = new CollectionView(_anlVTexte);
lstAnlVTexte.CurrentChanged += SelectionChanged; // raised after the current item has been changed.
lstAnlVTexte.CurrentChanging += SelectionChanging; // raise before changing the current item. Event handler can cancel this event.
}
private void SelectionChanged(object sender, EventArgs e)
{
var selectedItem = lstAnlVTexte.CurrentItem;
}
private void SelectionChanging(object sender, CurrentChangingEventArgs e)
{
}
private ICollectionView _lstAnlVTexte;
public ICollectionView lstAnlVTexte
{
get { return _lstAnlVTexte; }
set
{
_lstAnlVTexte = value;
OnPropertyChanged("lstAnlVTexte");
}
}
Here's a sample using the community toolkit mvvm and linq.
If you're not familiar, the toolkit does code generation.
This is a simple scenario to illustrate the approach.
Mainwindowviewmodel.
public partial class MainWindowViewModel : ObservableObject
{
[ObservableProperty]
private int selectedFilterInt = -1;
partial void OnSelectedFilterIntChanged(int newValue)
{
FilteredList = new ObservableCollection<MyItem>(FullList.Where(x=>x.Category == selectedFilterInt).ToList());
}
public List<int> FilterOptions { get; set; } = new List<int> {1,2,3};
private List<MyItem> FullList= new List<MyItem>
{
new MyItem{ Category = 1},
new MyItem{ Category = 1},
new MyItem { Category = 1 },
new MyItem { Category = 2 },
new MyItem { Category = 2 },
new MyItem { Category = 3 }
};
[ObservableProperty]
private ObservableCollection<MyItem> filteredList = new ObservableCollection<MyItem>();
public MainWindowViewModel()
{
FilteredList = new ObservableCollection<MyItem>(FullList);
}
}
There's a full list of all the items.
But a filtered list is going to be bound to the itemssource of my listbox ( equivalent to your datagrid ).
Due to the code generated, when selectedFilterInt changes, OnSelectedFilterIntChanged will be called. It's got a handler listening for property changed of SelectedFilterInt if you dug into the generated code.
That method uses a linq where to filter the full list into filtered list.
Setting that filtered list property raises property changed and the view re reads the new collection.
MainWindow. ( I did mention this is simplified )
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<StackPanel>
<ComboBox SelectedItem="{Binding SelectedFilterInt}"
ItemsSource="{Binding FilterOptions}"/>
<ListBox ItemsSource="{Binding FilteredList}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Category}"/>
<TextBlock Text="{Binding Comment}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Grid>
</Window>
and MyItem
public partial class MyItem : ObservableObject
{
[ObservableProperty]
private int category = 0;
[ObservableProperty]
private string comment = "Some test string";
}
Which is a bit underwhelming visually but works:
In your code you need to get all the data into a collection.
Call that FulList.
You then need another collection which will be the filtered data.
Call this FilteredList.
Bind itemssource to FilteredList
Initially, you presumably want FilteredList to be = FullList
Then when the user selects something in the combobox you need to react to that.
You could bind selecteditem to a property and act in the setter or handle propertychanged like my code does.
However you do it, you get a new integer.
You then use that to filter FullList into a new collection which will replace the bound FilteredList.
You also need to somehow have one entry per AnlV nr whatever that is in your combobox.
AnlV nr isn't going to work as a property name since it's got a space but it is the equivalent to Category in my sample.
You will use that selected value in the linq.
Substitute the name of that property for Category. Substitute the type of whatever your collection is. Maybe that's AnlVhistText. I'm not sure.
How can I add items to listbox when item source is a list.
XAML:
<ListBox Grid.Row="2" HorizontalAlignment="Stretch" ItemsSource="{Binding Source={StaticResource viewModel}, Path=CultureEvents}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Source={StaticResource viewModel}, Path=ItemTitle}" Height="30" HorizontalAlignment="Left" Margin="116,364,0,0" VerticalAlignment="Top" Width="334" Foreground="White" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In viewmodel I have list:
public List<CultureEvent> CultureEvents { get; set; }
And property:
public string ItemTitle
{
get
{
return ?;
}
set
{
? = value;
OnPropertyChanged(new PropertyChangedEventArgs("ItemTitle"));
}
}
But I don't know what to put into property.
private string _itemTitle
public string ItemTitle
{
get
{
return _itemTitle;
}
set
{
_itemTitle = value;
OnPropertyChanged(new PropertyChangedEventArgs("ItemTitle"));
}
You would generate The list something like this,
CultureEvents = new List<CultureEvent>();
CultureEvents.Add(new CultureEvent{Title = "Yourvalue"} );
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.
Please note:
The two types in the lists are simplified a lot for this example and must be kept separate.
The use of an int as connection between the types can not be changed.
The problem:
Given the code below, how do I get the ComboBox marked with ??? to:
Display the ColorDefs.Name as its content.
Set SelectedItem to the one where Models.DisplayColorNumber is equal to ColorDefs.ColorNumber.
Update the Models.DisplayColorNumber updated if the selection is changed.
In code-behind
public List<ModelData> Models { get; }
public List<DisplayColorDefinition> ColorDefs { get; }
DataContext=this;
XAML:
<ListBox ItemsSource="{Binding Models}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<ComboBox ??? />
<TextBlock Text="{Binding Models, Path=Name}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
ModelData type:
public class ModelData
{
private string name;
private int displayColorNumber;
public string Name
{
get { return name; }
set { name = value; }
}
public int DisplayColorNumber
{
get { return displayColorNumber; }
set { displayColorNumber = value; }
}
}
DisplayColorDefinition type:
public class DisplayColorDefinition
{
private int colorNumber;
private string name;
private Color displayColor;
public int ColorNumber
{
get { return colorNumber; }
set { colorNumber= value; }
}
public string Name
{
get { return name; }
set { name = value; }
}
public Color DisplayColor
{
get { return displayColor; }
set { displayColor = value; }
}
}
Use the SelectedValue and SelectedValuePath :
<ListBox ItemsSource="{Binding Models}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<ComboBox ItemsSource="{Binding Path=DataContext.ColorDefs, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
DisplayMemberPath="Name"
SelectedValue="{Binding Path=DisplayColorNumber}"
SelectedValuePath="ColorNumber"
/>
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding DisplayColorNumber}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
SelectedValue will be the property on the Model object, and SelectedValuePath will indicate which property of the DisplayColorDefinition to use for the binding.