Prism WPF Dynamic Regions - c#

Let's say we have a Prism 7 application with modules A and B. The main window has a Tab Control and two buttons, which add either module A or B to the Tab Control. I created a binding for the Tab Control items and implemented an item template, which includes Prism Region, whose name is bound to the item name.
<TabControl ItemsSource="{Binding Tabs}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl prism:RegionManager.RegionName="{Binding}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
The problem I am facing is that the region name doesn't seem to change, i.e. if I first add Module A, all the next button clicks will add Module A and vice versa. In my previous question Prism WPF Binding RegionManager.RegionName I was explained that I shouldn't bind the RegionManger.RegionName, so my question is, how should I implement the requirements?
Link to the repo: https://github.com/moisejbraver/ModulesTest

This is the way i handle tab controls with prism regions, i think this is helpful for you too.
<TabControl prism:RegionManager.RegionName="{x:Static local:RegionNames.AdvancedSetup}">
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Visibility" Value="{Binding DataContext.IsAvailable, Converter={coverters:BooleanToVisibilityConverter}}"/>
<Setter Property="Header" Value="{Binding DataContext.Name}"/>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
Then i register all my tabs to the same region in OnInitialized function of each module.
regionManager.RegisterViewWithRegion(RegionNames.AdvancedSetup, setupType);
I've made an interface which all viewmodels of tabs will implement,
it contains
Name of the tab
Whether the tab is visible.
You may need to change some details based on your need.

Related

TabControl - Display N TabItems depending on List<List<T>> dimension [duplicate]

If I have a class called: GuiObject, and that class has a list of GuiObjects called: "GuiObjects".
Now say my window has a list of GuiObjects, which I use in the .xaml file to dataBind to:
<StackPanel>
<ItemsControl ItemsSource="{Binding TopObjectList}" DataTemplateSelector="{DynamicResource templateSelector"/>
</StackPanel>
I can make a datatemplate for every type of FrameworkElement I want to generate, but I'm having trouble with the TabControl. I can create a datatemplate for the tabControl like so:
<DataTemplate x:key="TabControlTemplate" DataTemplateSelector="{DynamicResource templateSelector" >
<TabControl ItemsSource="{Binding GuiObjects}" />
</DataTemplate>
And the result is a tab control that has each of the proper pages present, but without the contents of the individual TabItems. Fair enough, I'll just make a DataTemplate for the TabItems. For each TabItem, I'd like to put the contents of GuiObjects into a stackpanel.
<DataTemplate x:key="TabItemTemplate" DataTemplateSelector="{Resource templateSelector">
<TabItem Header = {Binding Title}>
<StackPanel>
<ItemsControl ItemsSource="{Binding GuiObjects}" DataTemplateSelector="{DynamicResource templateSelector"/>
</StackPanel>
</TabItem>
</DataTemplate>
The problem here is that the TabItemTemplate never gets called. I've tried solutions that involve setting the ItemContainerStyle within the TabControlTemplate, but then I've got the problem of hierarchy. If I bind "GuiObjects" inside the content of the TabItem, I'm binding the list of tabItems, instead of the list that's within each TabItem. (I want to do the second one). Here's an example:
<DataTemplate x:key="TabControlTemplate" DataTemplateSelector="{DynamicResource templateSelector" >
<TabControl ItemsSource="{Binding GuiObjects}">
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding Title}"/>
<Setter Property="Content" Value="<StackPanel><ItemsControl ItemsSource="{Binding GuiObjects}" DataTemplateSelector="{DynamicResource templateSelector"/></StackPanel>"/>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
</DataTemplate>
Again, this solution has the levels problem: When I say: {Binding GuiObjets} I'm referring to the list of TabItems, instead of to the list of FrameworkElements within each TabItem.
The solution is either to stick with separate DataTemplates for both the TabControl and the TabItem, and just fix it so that the DataTemplateSelector actually works for the TabItems (no idea how to do this). Or to go with the ItemContainerStyle, and somehow tell it to go down one level when binding GuiObjects. Anyone know how to do this?
To provide a template for the contents of the pages of a TabControl, use the following properties:
ContentTemplate
ContentTemplateSelector
The ItemTemplate/ItemTemplateSelector properties of a TabControl are used to define what the tab headers look like.

Settings Window design - binding Page to TreeViewItem

I'm just starting with WPF (coming from WinForms) and I want to make settings window like the one in Visual Studio. Category tree on left side and appropriate views on the right.
In WinForms I did it by adding to TreeViewItem's TAG name of the window that should be displayed and then in OnClick I was creating that window using reflection. Something like this:
//pseudocode
TreeViewItem item = CreateTreeViewItem();
item.Tag = "GeneralSettingsWindow";
item.Text = "General settings";
------------------------------------------------------------------------
void ItemClick(object sender)
{
TreeViewItem item = sender as TreeViewItem;
string formName = item.Tag.ToString();
BaseSettingsForm f = Activator.CreateInstance(formName);
settingsPanel.Controls.Clear();
settingsPanel.Controls.Add(f);
}
And it worked fine.
I'm curious how can I achieve that functionality using WPF binding. Is it possible anyway?
I have done a tree view in left side and when you click an item it will update the contents of right side panel. In order to do that you have to make two panels or grid in an window, one in left side with less width and another in right side. Then put the tree view in the left side and update the contents of right side grid by adding different types of user controls or by showing and hiding different grids with the corresponding click of tree view item.
Here is an example of making the tree view and you can modify that according to your own requirement.
<TreeView MouseLeftButtonUp="Get_MFR" SelectedItemChanged="treeviewQuestion_SelectedItemChanged"
x:Name="treeviewQuestion" FontFamily="Nunito" FontSize="16" Padding="10 10"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
BorderThickness="0" Margin="0,0,0,27"
>
<TreeView.Resources>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="True"/>
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="10 2">
<TextBlock Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type TreeViewItem}},Path=Tag,
Converter={x:Static local:TagToTextConverter.Instance}}" />
<TextBlock Text="{Binding}"/>
<Rectangle Margin="5" Width="10" Height="10" Fill="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type TreeViewItem}},Path=Tag,
Converter={x:Static local:TagToColorConverter.Instance}}"/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TreeView.Resources>
</TreeView>
Even though you're new to WPF development, I'd still suggest you take a look at the MVVM design pattern. The separation between user interface and business logic makes this type of application structure much more straightforward.
Replace your settings panel with a ContentControl, and define as many DataTemplates as you require for different tree node types. You can then bind its Content property to the selected TreeViewItem.
Unfortunately, "out of the box" WPF doesn't support binding to the selected item in a TreeView. However, there are several ways around that, including using a Behaviour, which I described in a recent blog post.

Dynamically set UserControl as Listbox DataTemplate body

I have the following setup:
<ListBox ItemSource="{Binding Targets}">
<ListBox.ItemTemplate>
<DataTemplate>
<view:ViewName />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
What I am trying to accomplish is to dynamically decide which view to use at runtime, based on a property within the DataContext of the ListBox. In simple terms, I want to replace <view:ViewName> with a data binding that returns the proper view.
I use MEF to provide plug-ins for my app that may need to provide a custom view to display the items when appropriate. At design time I won't know all the possible view types (they may be dynamically loaded from a DLL) so a simple DataTemplateSelector won't do.
I have researched solutions but have come up empty.
Since you want to change templates based on a bound value, you can use a DataTrigger to determine the ContentTemplate of the ListBoxItem
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="ContentTemplate" Value="{StaticResource DefaultTemplate}"/>
<Style.Triggers>
<DataTrigger Property="{Binding SomeProperty}" Value="A">
<Setter Property="ContentTemplate" Value="{StaticResource TemplateA}"/>
</DataTrigger>
</Style.Triggers>
</Style>
I find this better than using a DataTemplateSelector because it gets re-evaluated if the bound property changes, while a DataTemplateSelector does not.
If you want to change templates based on an object type, you can use Implicit DataTemplates. These are DataTemplates that define a DataType, but no x:Key, and they will be used anytime WPF tries to draw an object of the specified type.
For example, if you had this template defined in your <X.Resources> somewhere
<DataTemplate DataType="{x:Type models:ActionA}">
<views:ActionAView />
</DataTemplate>
you could then insert your Model object directly into the UI and WPF would draw it using the template you specified
<ContentControl Content="{Binding SomeIActionObject}" />
<ItemsControl ItemsSource="{Binding CollectionOfIActionObjects}" />
Update
You mentioned that you would be allowing users to create modules with additional Templates that get imported using MEF, so in that case you would probably be better off using an IValueConverter that look up the matching template within Application.Resources
For example, if the bound value equals "A", then the converter might search Application.Resources for a template named "TemplateA" and return it to the binding
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="ContentTemplate"
Value="{Binding SomeProperty,
Converter={StaticResource MyTemplateConverter}}"/>
</Style>
Using the DataTemplateManager from this post You can do something like:
DataTemplateManager.RegisterDataTemplate<ViewModelType1, ViewType1>();
DataTemplateManager.RegisterDataTemplate<ViewModelType2, ViewType2>();
DataTemplateManager.RegisterDataTemplate<ViewModelType3, ViewType3>();
then you would remove the ItemTemplate from the ListBox:
<ListBox ItemSource="{Binding Targets}"/>
and in the ListBox ViewModel you could:
public void AddTargets()
{
Targets.Add(new ViewModelType1());
Targets.Add(new ViewModelType2());
Targets.Add(new ViewModelType3());
}
Then, each DataTemplate will be automatically used by WPF to render each corresponding ViewModel.
Also note that you can call DataTemplateManager.RegisterDataTemplate() at any time before showing the ListBox, so you can theoretically do that when loading the MEF parts.
Edit:
Based on your comment, you could create a single DataTemplate with a ContentPresenter to display the selected View according to a property in the ViewModel:
<DataTemplate DataType="{x:Type local:TargetViewModel}">
<ContentPresenter x:Name="MainContentPresenter" Content="{Binding}" ContentTemplate="{Binding YourProperty, Converter=SomeConverter}"/>
and inside the SomeConverter you should use the same technique as demonstrated in the post, to dynamically generate a DataTemplate.

Binding on a List<UserControl>

I got some binding issues...
Shapes is a list of customized UserControls, for example one of those UserControl can be an Ellipse with some specific properties. My goal is to loop on this list and show all the UserControls (it can be an Ellipse, or a Rectangle...) I just want to show the UserControl like if I would just prompt his XAML Content.
Here is my grid, i just don't know what to put in the DataTemplate, i tried some different things but nothing worked actually, hope some can help me :)
<Grid>
<s:ScatterView ItemsSource="{Binding Shapes}">
<s:ScatterView.ItemContainerStyle>
<Style TargetType="s:ScatterViewItem">
<Setter Property="Background" Value="Transparent"></Setter>
</Style>
</s:ScatterView.ItemContainerStyle>
<s:ScatterView.ItemTemplate>
<DataTemplate>
<class:Shape ShapeItem="{Binding}" />
</DataTemplate>
</s:ScatterView.ItemTemplate>
</s:ScatterView>
</Grid>
as mention in comment.if your ShapeItem is UserControl try to bind that with Content Property.

C#/XAML/WPF binding working partially, only displays first item in List

I have what should be a really simple binding, but the problem I'm seeing is that instead of displaying the three companies (company_list is a List, where Company contains a company_id to bind to), I see the window pop up with only the first company_id in company_list. I have other bindings which seem to work fine, and in some other cases I see that I've used ItemSource instead of DataContext, but when I use that I get "Items collection must be empty before using ItemsSource". I've searched extensively for a simple answer to this in stackoverflow, msdn and elsewhere, and have seen mostly really complex solutions that I haven't been able to understand/apply.
When my window appears, it has:
CompanyA
where it should have:
CompanyA
CompanyB
CompanyC
which is the content of the company_list (yes, verified in debugger). Suggestions appreciated! Code and XAML follow.
ReadMasterCompanyList(); // populates a_state.company_list with 3 companies
// display company list dialog
CompanySelect cs_window = new CompanySelect();
cs_window.CompanyListView.DataContext = a_state.company_list;
// fails: cs_window.CompanyListView.ItemsSource = a_state.company_list;
cs_window.Show();
And the XAML from CompanySelect:
<Grid>
<ListView IsSynchronizedWithCurrentItem="True"
x:Name="CompanyListView"
SelectionMode="Single" SelectionChanged="CompanyListView_SelectionChanged">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="Height" Value="30"/>
</Style>
</ListView.ItemContainerStyle>
<ListViewItem Content="{Binding Path=company_id}"></ListViewItem>
</ListView>
</Grid>
I would set the ItemsSource of the ListView, rather than the DataContext, either in codebehind:
cs_window.CompanyListView.ItemsSource = a_state.company_list;
or with binding:
<ListView ItemsSource="{Binding company_list}">
And then set the ItemTemplate of the ListView instead.
...
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding company_id}" />
</DataTemplate>
</ListView.ItemTemplate>
...
I would also look into using the MVVM design pattern for testability and separation of concerns, and look at using PascalCase for your property names.
Also, unless you specifically wanted a ListView, I would use a ListBox.
First, set the DataContext only after cs_window.Show().
Second, the ListViewItem you have as a child in your ListView's XAML is why you're only seeing one.
Third, might work better (and would be more MVVM-ish) if you define ItemsSource in the XAML, like this:
<ListView ItemsSource="{Binding Path=company_list}" ...>
That's after making a_state the DataContext of the ListView's container or some other ancestor element.
The problem is, that you define one ListViewItem in your XAML code. You shouldn't do this.
Try something like this:
<Grid>
<ListView IsSynchronizedWithCurrentItem="True"
x:Name="CompanyListView"
SelectionMode="Single" SelectionChanged="CompanyListView_SelectionChanged">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="Height" Value="30"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Content={Binding Path=company_id}/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>

Categories