C# / WPF: Binding TabControl - TabItem (Name) to HeaderedContentControl - Header - c#

I have a HeaderedContentControl in my application. In this HeaderedContentControl there is a TabControl.
Now I want to display the name of the selected tab (TabItem) in the header of the HeaderedContentControl (like in Visual Studio the Solution Explorer / Team Explorer e.g.).
My application is (partly) based on Josh Smiths WPF-MVVM-Example but I use additional Prism with Unity.
Furthermore I split the resources in some files.
Here is my MainView.xaml:
<UserControl x:Class="STController.ModuleAComport.View.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"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="300">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="..\Resources\MainViewResources.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.Resources>
</Grid.Resources>
<Border Grid.Row="0"
Grid.Column="0"
Style="{StaticResource MainBorderStyle}">
<HeaderedContentControl Header="?"
Style="{StaticResource MainHeadreredContentControlStyle}"
ContentTemplate="{StaticResource WorkspacesTemplate}"
Content="{Binding Path=Workspaces}" />
</Border>
</Grid>
</UserControl>
And here is my MainViewResources.xaml:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:view="clr-namespace:STController.ModuleAComport.View"
xmlns:viewmodel="clr-namespace:STController.ModuleAComport.ViewModel">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/STController.Resources;component/Themes/Generic.xaml" />
</ResourceDictionary.MergedDictionaries>
<!-- Put your not shared resource here -->
<DataTemplate DataType="{x:Type viewmodel:ComportViewModel}">
<view:ComportView />
</DataTemplate>
<DataTemplate DataType="{x:Type viewmodel:TestViewModel}">
<view:TestView />
</DataTemplate>
<DataTemplate x:Key="TabItemTemplate">
<Grid>
<ContentPresenter VerticalAlignment="Center"
Content="{Binding Path=DisplayName}" />
</Grid>
</DataTemplate>
<DataTemplate x:Key="WorkspacesTemplate">
<TabControl x:Name="TabControl"
IsSynchronizedWithCurrentItem="True"
TabStripPlacement="Bottom"
Style="{StaticResource MainTabControlStyle}"
ItemsSource="{Binding}"
Margin="4"
ItemTemplate="{StaticResource TabItemTemplate}"
ItemContainerStyle="{StaticResource MainTabItemStyle}">
</TabControl>
</DataTemplate>
</ResourceDictionary>
I had two different solution approaches:
1) Binding the SelectedIndex:
My first idea was to bind the SelectedIndex of the TabControl to a property in my viewmodel.
With the index I can then "select" the related view (viewmodel) and get the name and bind it to the header (see HeaderedContentControl; Content="{Binding Path=Workspaces}"; Workspaces is of type ObservableCollection)
But once I bind the SelectedIndex property of the TabControl, the TabControl does not switch reliable anymore. Sometimes when I click on the TabItem it is switching sometimes not. Sometimes I need to click ten or more times. A very strange behavior. There is no difference if I implement the property (SelectedIndex) in my viewmodel or not.
2) Elementbinding:
My second idea was to implement a ElementName-Binding
But as I expected this does not work (Visual / Logical Tree). The error is:
"Cannot find source for binding with reference 'ElementName=TabControl'.
BindingExpression:Path=ActualHeight; DataItem=null; target element is
'HeaderedContentControl' (Name=''); target property is 'Header' (type
'Object')"
In this case I also tried to move the TabControl into the Resources of the UserControl and Grid.
So the question is: Is it possible / how is it possible to show the name of the selected tab of the TabControl in the header of the HeaderedContentControl?
Is there a solution without code behind (I don't really like code behind ;) )?

Related

WPF - XAML Page Center to Window

I'm building a WPF app using .NET 4.0 and MVVM Light.
I have implemented navigation in the app using a single Window with a Frame that is changing based in my current view.
Here's the code I have in my MainWindow.xaml:
<Controls:MetroWindow x:Class="App.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Controls="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
xmlns:resx="clr-namespace:App.Resources"
xmlns:utils="clr-namespace:App.Utils"
Title="{Binding Path=Content.Title, ElementName=MainFrame}"
Height="{Binding Source={x:Static SystemParameters.PrimaryScreenHeight}, Converter={utils:RatioConverter}, ConverterParameter='0.9' }"
Width="{Binding Source={x:Static SystemParameters.PrimaryScreenWidth}, Converter={utils:RatioConverter}, ConverterParameter='0.9' }"
xmlns:iconPacks="http://metro.mahapps.com/winfx/xaml/iconpacks"
WindowStartupLocation="CenterScreen">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Fonts.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Controls.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Accents/Blue.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Accents/BaseLight.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid>
<Frame Source="\Views\LoginView.xaml" NavigationUIVisibility="Hidden" Name="MainFrame"></Frame>
</Grid>
</Controls:MetroWindow>
By default, the MainWindow is occupying the 90% of the screen. I would like to center the contents of the MainFrame inside the MainWindow.
Is it posible? How can I do it? I guess it's a simple task to do, but I've been looking for 1 hour and I couldn't find something specific.
Since you are using a grid, you can insert a stackpanel and center them out, like this:
<Grid VerticalAlignment="Center">
<StackPanel HorizontalAlignment="Center">
<Frame Source="\Views\LoginView.xaml" NavigationUIVisibility="Hidden" Name="MainFrame"></Frame>
</StackPanel>
</Grid>

c# mvvm binding button commandparameter to control outside compositeCollection

I'm just not getting it: I've a ribbon-menu where some tabs/groups/buttons are defined in xaml and some tabs/groups/buttons are defined during runtime (user loads a certain "command set"). To realise this I put the ribbonTab which is defined in xaml and a collection container inside a compositeCollection.
MainWindow.xaml
<RibbonWindow x:Class="test_ribbonButtonBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility /2006"
xmlns:local="clr-namespace:test_ribbonButtonBinding"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525" DataContext=" {StaticResource svm}">
<RibbonWindow.Resources>
<CollectionViewSource x:Key="tabs" Source="{Binding ribbon}"/>
</RibbonWindow.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Ribbon x:Name="RibbonMenu" Grid.Column="0" Grid.Row="0">
<Ribbon.ItemsSource>
<CompositeCollection>
<RibbonTab Header="Testtab" >
<RibbonGroup x:Name="Testgroup" Header="Testgroup">
<RibbonButton LargeImageSource="Icons/question.png" Label="TestAddCommand" Command="{Binding cmdTestAddCommand}" />
<RibbonButton LargeImageSource="Icons/question.png" Label="TestReadCommand" Command="{Binding cmdTestReadCommand}" CommandParameter="{Binding SelectedItem, ElementName=lstSequence}"/>
</RibbonGroup>
</RibbonTab>
<CollectionContainer Collection="{Binding Source={StaticResource tabs}}"/>
</CompositeCollection>
</Ribbon.ItemsSource>
</Ribbon>
<ListView x:Name="lstSequence" ItemsSource="{Binding sequence}" Grid.Row="1" Grid.Column="0" SelectionMode="Extended"/>
</Grid>
</RibbonWindow>
Problem now arises for the CommandParameter of the already defined buttons:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=lstSequence'. BindingExpression:Path=SelectedItem; DataItem=null; target element is 'RibbonButton' (Name=''); target property is 'CommandParameter' (type 'Object')
This might be simple but I just can't wrap my head around it: how do I bind the commandparameter to the selectedItems in the ListView. It's obviously the wrong dataContext.
What further confuses me: if I leave out the composite collection (so all tabs are just defined inside the xaml) the command binds correctly to the element inside the viewmodel and the commandparameter binds correctly to the elements in the window (how does this work anyway, should BOTH bind to the viewmodel as it is specified in the DataContext of the window???)
Any help appreciated.
Resu
EDIT:
Finally setting a setter property in the listview solved the problem
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
But I'm still wondering why my method doesn't work anypody else an explanaition for this?
Two options (untested):
1) use parent element to pass value via RelativeSource:
<Grid Tag={Binding SelectedItem, ElementName=lstSequence}>
...
<!-- hopefully there are no other grids on the way -->
<RibbonButton CommandParameter="{Binding Tag, RelativeSource={RelativewSource FindAncestor, AncestorType=Grid}}"
Command="{Binding cmdTestReadCommand}" .../>
...
<ListView x:Name="lstSequence" ... />
</Grid>
2) do not use parameter at all
<ListView SelectedItem={Binding SelectedItem, Mode=OneWayToSource} ... />
view model:
public whatevertype SelectedItem { get; set; }
inside command execute delegate
var parameter = SelectedItem;

how to access other resources inside a ResourceDictionary

A DataTemplate inside a Resource Dictionary needs to refer to a Styles.xaml, so I have the following
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:src="clr-namespace:WPFApp">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="resources/Styles.xaml" />
</ResourceDictionary.MergedDictionaries>
<DataTemplate DataType="{x:Type src:MyFileInfo}">
<Grid>
grid stuff
</Grid>
<TextBlock> stuff </TextBlock>
</DataTemplate>
</ResourceDictionary>
but there is an error at DataTemplate saying that The proprety "Visual Tree" can only be set once. What does this mean? Is it good practice to put a DataTemplate inside a ResourceDictionary? How to access other resources inside a ResourceDictionary?
A DataTemplate should only have one child. Use this:
<DataTemplate DataType="{x:Type src:MyFileInfo}">
<Grid>
grid stuff
<TextBlock> stuff </TextBlock>
</Grid>
</DataTemplate>

Cannot bind to properties outside Data Context

In my application I have a two TreeView objects that are bound to the same data.
I made a user control for the Tree which is called TreeView and looks like this:
<UserControl x:Class="MyApp.Views.TreeControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:model="clr-namespace:MyApp.Model"
xmlns:viewModel="clr-namespace:MyApp.ViewModels"
xmlns:views="clr-namespace:MyApp.Views"
xmlns:converters="clr-namespace:MyApp.Converters">
<UserControl.Resources>
<converters:EnumToPicConverter x:Key="Converter"></converters:EnumToPicConverter>
<!--Control colors.-->
<Style x:Key="MyTreeViewItemStyle" TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="{Binding Path=(model:TreeNode.IsExpanded), Mode=TwoWay}" />
</Style>
<HierarchicalDataTemplate DataType="{x:Type model:TreeNode}" ItemsSource="{Binding ChildListNodes}">
<HierarchicalDataTemplate.Triggers>
<DataTrigger Binding="{Binding IsEqual}" Value="false">
<Setter Property="TreeViewItem.Background" Value="Blue"></Setter>
</DataTrigger>
</HierarchicalDataTemplate.Triggers>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Path=EntityType,Converter={StaticResource Converter}}" />
<TextBlock Margin="5,0" Text="{Binding ItemName, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"/>
</StackPanel>
</HierarchicalDataTemplate>
</UserControl.Resources>
<TreeView ItemsSource="{Binding RootNode}" ItemContainerStyle="{StaticResource MyTreeViewItemStyle}" />
Now, In my main window, I use it in the following way:
<Window x:Class="MyApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:model="clr-namespace:MyApp.Model"
xmlns:viewModel="clr-namespace:MyApp.ViewModels"
xmlns:views="clr-namespace:MyApp.Views"
xmlns:converters="clr-namespace:MyApp.Converters"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<viewModel:TreeViewModel/>
</Window.DataContext>
<DockPanel>
<Grid DockPanel.Dock="Top" Name="LoadRow">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<views:DbChooser Path="{Binding Path1}" ReloadCommand="{Binding LoadFileACommand}" Grid.Column="0"/>
<views:DbChooser Path="{Binding Path2}" ReloadCommand="{Binding LoadFileBCommand}" Grid.Column="1"/>
</Grid>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<views:TreeControl Grid.Column="0" ItemName="{Binding Name1}"/>
<views:TreeControl Grid.Column="1" ItemName="{Binding Name2}"/>
</Grid>
</DockPanel>
Problem is that:
The DataContext is "viewModel:TreeViewModel" , but I want to bind each treeViewItem's Textblock content to a different property of "model:TreeNode" (which is the type of class of the TreeNodeItem holds..)
And in the main window it says "Cannot resolve property "Name1" in data context of type 'MyApp.ViewModels.TreeViewModel' .
I tried various different options and read few posts here in StackOverFlow but couldn't find a solution..
Thanks for any help.
edit, few clarifications:
1. TreeControl just wraps WPF TreeView (you can see the code is pasted..)
2. The DataContext of The MainWindow is TreeViewModel, it is a class that holds the Root Node of the tree (of type TreeNode) and few other properties I use. The Property that I want to "send" to the TreeControl is a property of type TreeNode, which is the type of the TreeViewItems
Another edit:
In other words, what I want to accomplish is that:
To "tell" to the first TreeControl " Please put in the text block of each TreeNodeItem the content of the the property 'Name1' "
And to "tell" the second TreeControl "Please put in the text block of each TreeNodeItem the content of the property 'Name2' "
I do not try to answer but perhaps give you some hints to debug.
You can use "DataContextChanged" event on most xaml tag to make sure you get the DataContext you expect.
If you have the expected DataContext but got warning in xaml. You could use:
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
and set:
<AnyTagThatHasDataContext d:DataContext="{d:DesignInstance Type=youDataContextObject}">
That will help to design your window/UserControl

WPF UserControl not rendering. UserControl is instantiated using DataTemplates

I have a user control that I`m trying to show via data templates. Unfortunately, the control never shows. Here's the DataTemplate for it:
<DataTemplate x:Key="listViewTemplate">
<ctrls:SpecialControl/>
</DataTemplate>
If I put regular controls inside those DataTemplate tags, I can see them. My SpecialControl however, won't show. Here's the xaml file for my SpecialControl:
<UserControl x:Class="CustomControls.SpecialControl"
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"
mc:Ignorable="d">
<StackPanel>
<CheckBox Content="Hello World" />
<Button >
<TextBlock Text="Goodbye world"/>
</Button>
</StackPanel>
</UserControl>
For some reason, this control is invisible at run time. If I put them directly into the template, I'll see them. I know I could just do that, but I want to do something more complex with this class, using databinding, and custom behavior. I tried using Snoop, and I can see my SpecialControl in there somewhere, with a ContentPresenter that's not expandable: no sign of The checkbox or the button.
Edit:
Here is the View that is using the SpecialControl: I left out many of the templates I'm using, because I don't want this to be too crowded.
<UserControl x:Class="Tools.EditorWindow"
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:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
xmlns:ctrls="clr-namespace:CustomControls"
xmlns:local="clr-namespace:Tools"
mc:Ignorable="d"
x:Name="This">
<UserControl.Resources>
<!--More templates -->
<DataTemplate x:Key="groupBoxTemplate" >
<ctrls:SpecialGroupBox Header="{Binding Path=Title}" Margin="2" DockPanel.Dock="Top">
<ItemsControl ItemsSource="{Binding guiItemsList}" ItemTemplateSelector="{DynamicResource guiTemplateSelector}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel>
<!-- Set up the width of the individual items based on how many columns we are supposed to have and what the adjusted width of the wrapPanel is. This way the 4th item will be placed on the 2nd row if the ColumnCount is 3. -->
<WrapPanel.ItemWidth>
<MultiBinding Converter="{StaticResource itemWidthConverter}">
<Binding Path="ColumnCount"/>
<Binding Path="ActualWidth" RelativeSource="{RelativeSource Self}"/>
</MultiBinding>
</WrapPanel.ItemWidth>
</WrapPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ctrls:SpecialGroupBox>
</DataTemplate>
<DataTemplate x:Key="listViewTemplate">
<ctrls:SpecialControl/>
</DataTemplate>
<local:GuiTemplateSelector
... <!--More templates -->
GroupBoxTemplate="{StaticResource groupBoxTemplate}"
SpecialTemplate="{StaticResource listViewTemplate}"
x:Key="guiTemplateSelector"/>
</UserControl.Resources>
<DockPanel>
<ScrollViewer VerticalScrollBarVisibility="Auto" Grid.Row="1">
<StackPanel Name="rootPanel" DockPanel.Dock ="Top" Width="Auto" Height="Auto">
<ItemsControl ItemsSource="{Binding guiItemsList}" ItemTemplateSelector=" {StaticResource guiTemplateSelector}">
</ItemsControl>
</StackPanel>
</ScrollViewer>
</DockPanel>
</UserControl>
If you're wondering what the SpecialGroupBox does, it holds other controls, positioning them a certain way. There are a few of them in this window, and they work. It is inside of one of these SpecialGroupBoxes that my SpecialControl is supposed to appear.
Does your UserControl have a corresponding .xaml.cs file? This problem seems to happen when it doesn't.
If you're using Visual Studio, try copying all the XAML from your UserControl, then deleting it from your project, adding a new UserControl (with the same name as before), then pasting your content back into the XAML file. This will ensure that you have the correct .xaml.cs file set up.
See this answer.
In your post you didn't mention how you are using the listViewTemplate . if your using inside listview/listbox you can try like this it will load the user control:
<ListView ItemsSource="{Binding Items}">
<ListView.ItemTemplate>
<DataTemplate >
<ContentControl ContentTemplate="{StaticResource listviewDataTemplate}" Content="{Binding}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

Categories

Resources