I've been struggling since yesterday to build a (what I thought) simple Ribbon on WPF, using MVVM.
I found quite a few links on the internet (and on Stack Overflow), but none could really solve my problem.
In a nutshell my question is: Am I doing it wrong, or is it just plain impossible to bind a Ribbon to a ViewModel like I'm doing?
In Details:
This is my model for the menu: Each MenuGroup should be rendered as a RibbonGroup, and MenuGroup contains a collection of MenuItem which should be rendered as a RibbonButton (inside their respective RibbonGroup of course).
using Caliburn.Micro;
namespace MyCompany.Poc.Wpf.Models
{
public class MenuGroup
{
public MenuGroup()
{
Items = new BindableCollection<MenuItem>();
}
public string Name { get; set; }
public IObservableCollection<MenuItem> Items { get; set; }
}
}
using System.Windows.Input;
namespace MyCompany.Poc.Wpf.Models
{
public class MenuItem
{
public MenuGroup Group { get; set; }
public string Name { get; set; }
public string Link { get; set; }
public ICommand OpenMenuUrl { get; set; }
}
}
My ViewModel just contains a collection of those MenuGroup, for the binding on the XAML:
public IObservableCollection<MenuGroup> LegacyMenuItems
{
get { return _legacyMenuItems; }
}
The collection is populated (for the time being) as soon as the VM is instanciated, with fake data):
var group1 = new MenuGroup();
group1.Name = "Group 1";
var group2 = new MenuGroup();
group2.Name = "Group 2";
group1.Items.Add(new MenuItem {Group = group1, Link = "http://www.google.co.uk", Name = "Link 1", OpenMenuUrl = OpenMenuUrl});
group1.Items.Add(new MenuItem {Group = group1, Link = "http://www.google.co.uk", Name = "Link 2", OpenMenuUrl = OpenMenuUrl});
group2.Items.Add(new MenuItem {Group = group2, Link = "http://www.google.co.uk", Name = "Link 3", OpenMenuUrl = OpenMenuUrl});
group2.Items.Add(new MenuItem {Group = group2, Link = "http://www.google.co.uk", Name = "Link 4", OpenMenuUrl = OpenMenuUrl});
LegacyMenuItems.Add(group1);
LegacyMenuItems.Add(group2);
Now the XAML:
<my:Ribbon DockPanel.Dock="Top">
<my:RibbonTab Header="Legacy A.I." ItemsSource="{Binding LegacyMenuItems}">
<my:RibbonTab.ItemTemplate>
<DataTemplate>
<my:RibbonGroup Header="{Binding Name}" ItemsSource="{Binding Items}">
<my:RibbonGroup.ItemTemplate>
<DataTemplate>
<my:RibbonButton Label="{Binding Name}" Command="{Binding OpenMenuUrl}" CommandParameter="{Binding Link}"></my:RibbonButton>
</DataTemplate>
</my:RibbonGroup.ItemTemplate>
</my:RibbonGroup>
</DataTemplate>
</my:RibbonTab.ItemTemplate>
</my:RibbonTab>
<my:RibbonTab Header="Static">
<my:RibbonGroup Name="Administration" Header="Admin">
<my:RibbonButton Label="Stuffs" Command="{Binding Path=OpenMenuUrl}" CommandParameter="http://www.mycompany.com/somelink"></my:RibbonButton>
<my:RibbonButton Label="Google" Command="{Binding Path=OpenMenuUrl}" CommandParameter="http://www.google.co.uk"></my:RibbonButton>
</my:RibbonGroup>
</my:RibbonTab>
</my:Ribbon>
And what it renders:
As you can see, the "Static" tab works well and the "title" for the ribbon group ("Admin") is displayed at the right place.
Now, the "Legacy A.I." tab, has no buttons (there should be 2 buttons in each group), and the RibbonGroup titles are displayed funny (below where they should be).
If you have any clue on what I'm doing wrong here, please tell me :)
I'm very new on the WPF world, so I obviously don't understand templating properly...
A few facts to help you make a call:
- The Model is loaded correctly, since we can see "Group 1" and "Group 2" which are part of the fake data loaded in the ViewModel
- I tried Telerik's RadRibbon and it behaves exactly the same! So unless they are BOTH wrong at the same place, the problem must comes from me
Good night and good luck :)
I am using the ribbon also in a MVVM application and decided to stick with controls defined in XAML linking them to static ICommand properties in classes defined in my viewmodel layer. This is very inflexible but works for me so far.
If you have downloaded and installed the official Microsoft Ribbon you will find it's source and some sample's in:
C:\Program Files\Microsoft Ribbon for
WPF\MicrosoftRibbonForWPFSourceAndSamples
This includes a sample that also implements MVVM. This uses a totally different method though an may not be what you need, but I think it may be helpful to look it over.
I had the same problem yesterday and struggled a lot to find a solution.
I think your problem could be solved with a HierarchicalDataTemplate.
<r:Ribbon>
<r:RibbonTab Header="Legacy A.I." ItemsSource="{Binding LegacyMenuItems}">
<r:RibbonTab.Style>
<Style TargetType="{x:Type r:RibbonTab}">
<Setter Property="ItemsSource" Value="{Binding LegacyMenuItems}" />
<Setter Property="ItemTemplate">
<Setter.Value>
<HierarchicalDataTemplate DataType = "{x:Type r:RibbonGroup}"
ItemsSource = "{Binding Items}" >
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</Setter.Value>
</Setter>
</Style>
</r:RibbonTab.Style>
</r:RibbonTab>
</r:Ribbon>
Captain Necroposter here:
I had the same rendering issue and this is my solution
<HierarchicalDataTemplate DataType="{x:Type navigation:RibbonTabViewModel}" ItemsSource="{Binding Path=Groups}">
<TextBlock Text="{Binding Path=Title}"></TextBlock>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type navigation:RibbonGroupViewModel}" ItemsSource="{Binding Path=Items}">
<TextBlock Text="{Binding Path=Title}" ></TextBlock>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type navigation:RibbonButtonViewModel}">
<RibbonButton Label="{Binding Path=Title}" Command="{Binding Path=ClickedCommand}" CommandParameter="{Binding Path=.}"></RibbonButton>
</DataTemplate>
To begin with i had a RibbonGroup as template content for my RibbonGroupViewModel. Thanks to latest VS i was able to inspect the live tree and find the issue there.
Related
I have a RibbonComboBox that is used to set font sizes. It has a RibbonGallery that lists the various font sizes, displayed in the appropriate FontSize:
<r:RibbonComboBox DataContext="{x:Static vm:RibbonDataModel.FontSizeComboBoxData}"
SelectionBoxWidth="30">
<r:RibbonGallery MaxColumnCount="1"
Command="{Binding Command}"
CommandParameter="{Binding SelectedItem}">
<r:RibbonGallery.GalleryItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding}"
FontSize="{Binding}" />
</Grid>
</DataTemplate>
</r:RibbonGallery.GalleryItemTemplate>
</r:RibbonGallery>
</r:RibbonComboBox>
EDIT Here is my ViewModel:
public static RibbonDataModel
{
public static GalleryData<object> FontSizeComboBoxData
{
get
{
lock (LockObject)
{
const string key = "Font Size";
if (!DataCollection.ContainsKey(key))
{
var value = new GalleryData<object>
{
Command = HtmlDocumentCommands.ChangeFontSize,
Label = "Change Font Size",
ToolTipDescription = "Set the font to a specific size.",
ToolTipTitle = "Change Font Size",
};
var fontSizes = new GalleryCategoryData<object>();
var i = 9.0;
while (i <= 30)
{
fontSizes.GalleryItemDataCollection.Add(i);
i += 0.75;
}
value.CategoryDataCollection.Add(fontSizes);
DataCollection[key] = value;
}
return DataCollection[key] as GalleryData<object>;
}
}
}
}
Everything works as expected, but after I select an item from the gallery, it shows up in the RibbonComboBox with the same huge (or tiny) FontSize as it uses in the gallery.
How can I "reset" the FontSize of the selected item to the default when it's displayed in the RibbonComboBox?
The RibbonComboBox uses a ContentPresenter to show the item you select in the RibbonGallery.
Moreover the ContentPresenter adopts the same ItemTemplate that you declared in the RibbonGallery.
This is the "core" reason of your problem.
So you can choose between two solutions to workaround the problem.
FIRST SOLUTION (the fastest one)
You can simply set the IsEditable property of your RibbonComboBox to "true". In this way the RibbonComboBox replaces the ContentPresenter with a TextBox, without using any ItemTemplate. Then the font will have the right size.
SECOND SOLUTION (the best one IMHO)
Since the ItemTemplate is used at the same from both the RibbonComboBox's ContentPresenter and the RibbonGallery, it is the point where we can try to solve the problem. The olny difference is that when the DataTemplate is placed inside the RibbonGallery, its parent is a RibbonGalleryItem.
So if its parent is not a RibbonGalleryItem, you automatically know that the DataTemplate is placed inside the ContentPresenter.
You can handle this situation by writing a simple DataTrigger.
Let's see all in the code.
I wrote a simplified ViewModel:
namespace WpfApplication1
{
public class FontSizes
{
private static FontSizes instance = new FontSizes();
private List<double> values = new List<double>();
public FontSizes()
{
double i = 9.0;
while (i <= 30)
{
values.Add(i);
i += 0.75;
}
}
public IList<double> Values
{
get
{
return values;
}
}
public static FontSizes Instance
{
get
{
return instance;
}
}
}
}
Then this is my View:
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ribbon="http://schemas.microsoft.com/winfx/2006/xaml/presentation/ribbon"
xmlns:vm="clr-namespace:WpfApplication1"
Title="Window1" Height="300" Width="300">
<Window.Resources />
<DockPanel>
<ribbon:RibbonComboBox Label="Select a font size:"
SelectionBoxWidth="62"
VerticalAlignment="Center">
<ribbon:RibbonGallery MaxColumnCount="1">
<ribbon:RibbonGalleryCategory DataContext="{x:Static vm:FontSizes.Instance}" ItemsSource="{Binding Path=Values, Mode=OneWay}">
<ribbon:RibbonGalleryCategory.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Name="tb" Text="{Binding}" FontSize="{Binding}" />
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ribbon:RibbonGalleryItem, AncestorLevel=1}}"
Value="{x:Null}">
<Setter TargetName="tb" Property="FontSize" Value="12" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ribbon:RibbonGalleryCategory.ItemTemplate>
</ribbon:RibbonGalleryCategory>
</ribbon:RibbonGallery>
</ribbon:RibbonComboBox>
</DockPanel>
</Window>
As you can see the DataTrigger is the "component" which makes the "dirty job".
Now you just need to make you your mind about which solution you prefer.
I would advise you to use the Fluent.Ribbon library instead of the Microsoft Ribbons (as they are very buggy, not well maintained and only support old styles, really trust me on this one it will just save you much trouble).
Then you simply can use this code:
<fluent:ComboBox Header="Font Size" ItemsSource="{Binding FontSizes}">
<fluent:ComboBox.ItemTemplate>
<ItemContainerTemplate>
<TextBlock FontSize="{Binding }" Text="{Binding }" />
</ItemContainerTemplate>
</fluent:ComboBox.ItemTemplate>
</fluent:ComboBox>
And get the desired result:
Struggling with showing nested relationship in my TreeView. Here is the scenario:
In the database I have Category and Account tables. Each category can have zero or more sub-categories so this table has a nested relationship with itself. Each category/sub-category can have zero or more Account in it, there is a one-to-many relation between Category and Account. Simple, isn't it!
On top of my DB, I have EDMX, with Categories and Accounts entities in it and their associations as I mentioned above. For ease of understanding, I have renamed navigation properties so that Categories now has ParentCategory, ChildCategories and Accounts properties in it.
On top of EDMX, I have my ViewModel, which defines a public property named AllCategories. My TreeView will bind to this property. I initialize this property at the startup like this:
using (MyEntities context = new MyEntities())
Categories = context.Categories.Include(x => x.Accounts).ToList();
Finally I use the following HierarchicalDataTemplate to show this stuff:
<HierarchicalDataTemplate DataType = "{x:Type local:Category}" ItemsSource = "{Binding Path=ChildCategories}">
<TreeViewItem Header="{Binding Name}" ItemsSource="{Binding Accounts}" />
</HierarchicalDataTemplate>
<DataTemplate DataType = "{x:Type local:Account}">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
This runs fine and shows categories, sub-categories and accounts in the tree, but the problem is that sub-categories show up not only under their parent category, but also at the root-level. This happens for categories of all depths. What am I doing wrong here?
Note: If I add .Where(x=>!x.ParentID.HasValue) in the VM, it shows only the root category and its immediate children, nothing else.
Edit
Here's what it currently looks like. Everything goes fine up to the dotted white line (I added that line manually for illustration; has nothing to do with WPF). After that, the sub-categories start repeating with their child sub-categories. This process continues over and over till the leaf sub-categories. I believe I understand what's going on here, but don't have a solution for it. For reference, this guy presents a solution of the problem, but he is using DataSets, whereas I'm working with EF and can't translate his solution into my scenario.
The idea is to connect your business data by ObservableCollections and leave your Hierarchical templates simple, so that the treeview won't show duplicate entries.
The sample code shows nested viewmodel relationship and the corresponding hierarchical templates. For simplification, the Root is an ObservableCollection (otherwise you would need to add INotifyPropertyChanged here and selective ItemsSource Binding in the TreeView)
<Window x:Class="MyWpf.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyWpf"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<HierarchicalDataTemplate DataType = "{x:Type local:RootItem}" ItemsSource = "{Binding Path=Categories}">
<TextBlock Text="{Binding Header}"></TextBlock>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType = "{x:Type local:CatNode}" ItemsSource = "{Binding Path=Items}">
<TextBlock Text="{Binding Header}"></TextBlock>
</HierarchicalDataTemplate>
</Window.Resources>
<Grid>
<TreeView ItemsSource="{Binding MyRoot}"/>
</Grid>
</Window>
namespace MyWpf
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
MyRoot = new ObservableCollection<RootItem>();
MyRoot.Add(new RootItem());
}
public ObservableCollection<RootItem> MyRoot { get; set; }
}
public class RootItem
{
public RootItem()
{
Categories = new ObservableCollection<CatNode>();
Categories.Add(new CatNode { Header = "Cat1" });
Categories[0].Items.Add("Item11");
Categories[0].Items.Add("Item12");
Categories.Add(new CatNode { Header = "Cat2" });
Categories[1].Items.Add("Item21");
Categories[1].Items.Add("Item22");
}
public string Header { get { return "Root"; }}
public ObservableCollection<CatNode> Categories { get; set; }
}
public class CatNode
{
public CatNode()
{
Items = new ObservableCollection<string>();
}
public string Header { get; set; }
public ObservableCollection<string> Items { get; set; }
}
}
Is it the intended behavior that a binding to a collection automatically uses the first item as source?
Example Xaml:
<Window x:Class="ListSelection.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel>
<TextBlock Text="{Binding ColContent}" />
<TextBlock Text="{Binding ItemContent}" />
</StackPanel>
</Window>
and Code:
using System.Collections.Generic;
using System.Windows;
namespace ListSelection
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MyCol("col 1")
{
new MyItem("item 1"),
new MyItem("item 2")
};
}
}
public class MyItem
{
public string ItemContent { get; set; }
public MyItem(string content)
{
ItemContent = content;
}
}
public class MyCol : List<MyItem>
{
public string ColContent { get; set; }
public MyCol(string content)
{
ColContent = content;
}
}
}
The UI shows up with:
col 1
item 1
The second binding took implicitly the first collection item as source! So bug, feature or intended?
EDIT: .net 4.5, VS2012, corrections
EDIT 2:
I further investigated the problem together with a mate and got closer to the solution:
<Window x:Class="ListSelection.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel>
<ListView ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ItemContent}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<TextBlock Text="{Binding ItemContent}" />
</StackPanel>
</Window>
The - lets call it - magic binding seems to exist for master detail views. By default any collection that is bound gets a CollectionView - which provides a selected item property (and other cool stuff like sorting). This selected item can be used shortcutted for the detailed view. If the IsSynchronizedWithCurrentItem is set to true the shortcutted binding reacts to changed selections. The problem in the whole thing: the selected item of the CollectionView is alway set to the first item which leads to the magic binding... I would call that a bug and it should only work explicitly, e.g. by binding the collection to a Selector with the IsSynchronizedWithCurrentItem set.
I'm not sure how to describe my scenario in the title, so forgive me for the bad title.
My scenario:
MainView:
<Grid>
<TabControl ItemsSource="{Binding Tabs}"
SelectedIndex="0">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ViewName}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl x:Name="SamplesContentControl"
Content="{Binding View}"
VerticalContentAlignment="Stretch"
HorizontalContentAlignment="Stretch"/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
MainViewModel:
public class MainViewModel
{
public List<Tab> Tabs { get; set; }
IUnityContainer container;
public MainViewModel(IUnityContainer container)
{
this.container=container;
Tabs = new List<Tab>();
Tabs.Add(new Tab() { ViewName = "Test1", View = this.container.Resolve<TestView>() });
Tabs.Add(new Tab() { ViewName = "Test2", View = this.container.Resolve<TestView>() });
Tabs.Add(new Tab() { ViewName = "Test3", View = this.container.Resolve<TestView>() });
}
}
TestView is a ListView, I want the 3 views have different data. For example, Test1 view has Test1's data and Test2View has Test2's data. But I don't know how to achieve this.
TestViewModel:
public class TestViewModel
{
public ObservableCollection<Test> Tests{ get; set; }
public TestViewModel(ITestDataService testDataService)
{
Tests= new ObservableCollection<Test>(testDataService.GetTests());
}
}
TestView:
<ListView ItemsSource="{Binding Samples}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Title}" Margin="8"/>
<TextBlock Text="{Binding Summary}" Margin="8,0,8,8"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Anyone can help?
You may consider two alternatives for the issue you mentioned:
A simple workaround but not completely elegant, would be to rename the TestView and create the 3 different Test views, where each one would know what ViewModel would bind to its DataContext.
However, on the other hand you could keep the one only generic TestView and handle each instance's DataContext from the MainViewModel constructor. As the MainViewModel class is adding all the TestView instances into the TabList, that would be the place where DataContext of every TestView instance would be set. The MainViewModel would be responsible for the creation of every TestView and the manager of the corresponding ViewModel's DataContext of the Views.
Therefore, you could resolve the TestView instance and set its DataContext with the proper ViewModel before the NewTab sentences.
As a personal opinion, the second approach may be cleaner. Speccially if a fourth TestView would be needed and you would not need to create a new View type.
UPDATE:
Regarding the second solution of setting the DataContext in the MainViewModel, the code may look as follows:
public class MainViewModel
{
public List<Tab> Tabs { get; set; }
IUnityContainer container;
public MainViewModel(IUnityContainer container)
{
this.container = container;
TestView view1 = this.container.Resolve<TestView>();
view1.DataContext = this.container.Resolve<Test1ViewModel>();
TestView view2 = this.container.Resolve<TestView>();
view2.DataContext = this.container.Resolve<Test2ViewModel>();
TestView view3 = this.container.Resolve<TestView>();
view3.DataContext = this.container.Resolve<Test3ViewModel>();
Tabs = new List<Tab>();
Tabs.Add(new Tab() { ViewName = "Test1", View = view1 });
Tabs.Add(new Tab() { ViewName = "Test2", View = view2 });
Tabs.Add(new Tab() { ViewName = "Test3", View = view3 });
}
}
As you may see, the concept would be that the MainViewModel creates every tab with each TestView as described in the question, and it would also manage the configuration of their DataContext Property. Taking into account that setting the DataContext would be part of the creation of the View, the MainViewModel would remain responsible for the complete creation of every TestView with its corresponding DataContext.
I would like to clarify that the ViewModel being set on each DataContext would be the corresponding TestViewModel and not the MainViewModel itself. This way, the MainViewModel would be able to resolve every Test instance with the specific settings for each TestView.
Trying to use a generic ViewModel instead, it would also be necessary to configure each instance, which would add more unclean code than just setting the DataContext. Based on my understanding, it would be good to encapsulate each Test behavior on different ViewModels with descriptive names rather than one generic ViewModel.
I hope I have clarified the suggested approach.
Regards.
I am not sure if I do 100% understand your question but I give it a try.
ObservableCollection<Test1Value> data1 = new ObservableCollection<Test1Value>(new Test1Value[]
{
new Test1Value("Location1", 23.5),
new Test1Value("Location2", 52.5),
new Test1Value("Location3", 85.2)
});
ObservableCollection<Test2Value> data2 = new ObservableCollection<Test2Value>(new Test2Value[]
{
new Machine("Machine1", "OK"),
new Machine("Machine2", "not OK"),
new Machine("Machine3", "OK"),
new Machine("Machine4", "open")
});
CompositeCollection coll = new CompositeCollection();
coll.Add(new CollectionContainer() { Collection = data1 });
coll.Add(new CollectionContainer() { Collection = data2 });
Data = coll;
<ItemsControl ItemsSource="{Binding Data}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type local:Test1Value}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}"/>
<TextBlock Text=" ("/>
<TextBlock Text="{Binding MessuredValue}"/>
<TextBlock Text=")"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Test2Value}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Machine}"/>
<TextBlock Text=" - "/>
<TextBlock Text="{Binding Status}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
You can solve this with 1 viewmodel which holds different collections of different test values.
With binding it to a CompositeCollection the ListView or ItemsControl will pick up the right view (data template) for the right class (model).
Find more infos on the CompositeCollection here: http://msdn.microsoft.com/en-us/library/system.windows.data.compositecollection.aspx
Or look at how to bind a How do you bind a CollectionContainer to a collection in a view model? here: How do you bind a CollectionContainer to a collection in a view model?
I think you need to transfer this to prism but the concept should work the same way... =)...
HTH
It's a bit strange, but I really can't find a working example anywhere.
By the way, I'm using a ViewModel-first approach (in WPF) if this is important.
Thank you in advance.
If you have a look at the discussion here you will see that the intent of AllActive is to compose several Views/ViewModels into a containing ViewModel. Judging from your previous comments it seems as if this is what you were expecting but I figured I'd at least reference it here.
You then mention activating 3 different ViewModels at different regions of the View. The way I've handled this in the past is to have separate properties for binding/referencing the ViewModels in the View, and then just adding all of them to Items to get the Conductor behavior.
public sealed class MyViewModel : Conductor<Screen>.Collection.AllActive
{
public MyViewModel(IMagicViewModelFactory factory)
{
FirstSubViewModel = factory.MagicallyGiveMeTheViewModelIWant();
SecondSubViewModel = factory.MagicallyGiveMeTheViewModelIWant();
ThirdSubViewModel = factory.MagicallyGiveMeTheViewModelIWant();
Items.Add(FirstSubViewModel);
Items.Add(SecondSubViewModel);
Items.Add(ThirdSubViewModel);
}
public Screen FirstSubViewModel { get; private set; }
public Screen SecondSubViewModel { get; private set; }
public Screen ThirdSubViewModel { get; private set; }
}
And in MyView you would have something like this. Of course you could put these ContentControls wherever you want to in the view.
<StackPanel>
<ContentControl x:Name="FirstSubViewModel" />
<ContentControl x:Name="SecondSubViewModel" />
<ContentControl x:Name="ThirdSubViewModel" />
</StackPanel>
Another common use for AllActive is when you have a list of items. But the items are complex enough to warrant having their own View/ViewModels that require activation. In that case you would not have to have separate properties for the views as you would just set the x:Name of the list control to Items.
You can do implement like below, use TreeViewModel at the place of TabViewModel
ShellView
<DockPanel>
<Button x:Name="OpenTab"
Content="Open Tab"
DockPanel.Dock="Top" />
<TabControl x:Name="Items">
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding DisplayName}" />
<Button Content="X"
cal:Message.Attach="DeactivateItem($dataContext, 'true')" />
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
</DockPanel>
ViewModel
public class ShellViewModel : Conductor<IScreen>.Collection.AllActive {
System.Collections.Generic.List<TabViewModel> tabViewModelCollection = new System.Collections.Generic.List<TabViewModel>();
public void ActiveAllTab() {
foreach (var tabViewModel in tabViewModelCollection)
{
ActivateItem(tabViewModel);
}
}
}