I have created a TabControl whose tabs are bound to UserControls
<Window x:Class="_MainWindow"...>
....
<TabControl ItemContainerStyle="{StaticResource TabItemStyleVM}" x:Name="tc" Margin="1" SelectedIndex="0" Height="545"
ItemsSource="{Binding Path=TabItems}"
SelectedItem="{Binding Path=SelectedTabItem}"
IsSynchronizedWithCurrentItem="True">
<TabControl.Resources>
<DataTemplate x:Key="0">
<my:myUsrControl0 x:Name="myUsrControl0" DataContext="{Binding ....}"/>
</DataTemplate>
<DataTemplate x:Key="1">
<my:myUsrControl1 x:Name="myUsrControl1" DataContext="{Binding ...}"/>
</DataTemplate>
<DataTemplate x:Key="2">
<my:myUsrControl2 x:Name="myUsrControl1" DataContext="{Binding ...}"/>
</DataTemplate>
...
</TabControl.Resources>
</TabControl>
....
In Window CodeBehind, Im able to change TabControl content dinamically, depending on Tc SelectedIndex
Public Class _MainWindow
Private Sub _MainWindow_Loaded(ByVal sender As System.Object, ByVal e As RoutedEventArgs) Handles Me.Loaded
...
End Sub
Private Sub tc_SelectionChanged(ByVal sender As Object, ByVal e As SelectionChangedEventArgs) Handles tc.SelectionChanged
If Not IsNothing(CType(sender, TabControl)) Then
CType(sender, TabControl).ContentTemplate = TryCast(sender.FindResource(CType(sender, TabControl).SelectedIndex().ToString), Object)
...
End If
End Sub
...
End Class
Now, I should place a new tab between two existing ones (eg: between tabs "1" and "2"). So, I rearranged TabControl like this
<TabControl ItemContainerStyle="{StaticResource TabItemStyleVM}" x:Name="tc" Margin="1" SelectedIndex="0" Height="545"
ItemsSource="{Binding Path=TabItems}"
SelectedItem="{Binding Path=SelectedTabItem}"
IsSynchronizedWithCurrentItem="True">
<TabControl.Resources>
<DataTemplate x:Key="0">
<my:myUsrControl0 x:Name="myUsrControl0" DataContext="{Binding ....}"/>
</DataTemplate>
<DataTemplate x:Key="1">
<my:myUsrControl1 x:Name="myUsrControl1" DataContext="{Binding ...}"/>
</DataTemplate>
<DataTemplate x:Key="10"> <----------- NEW TAB
<my:myUsrControl10 x:Name="myUsrControl10" DataContext="{Binding ...}"/>
</DataTemplate>
<DataTemplate x:Key="2">
<my:myUsrControl2 x:Name="myUsrControl2" DataContext="{Binding ...}"/>
</DataTemplate>
...
</TabControl.Resources>
</TabControl>
at runtime, when I click on new Tab Item "10", the tc_selectionChanged detects tc.SelectedIndex = 2, and fills TabControl content to the UserControl bound to it (ie: myUsrControl2).
I wish to manage TC content upon selected DataTemplate key: how can I get it?
Related
Recently, I learned a lot about MVVM / Binding / Entityframework etc. And as I always go for the hard way - I use VB.NET - and have to convert most of the code found from C# to VB.NET ;)
So, what's my Topic:
Complete Titel:
WPF Hierarchical Treeview: Combined binding and templating of an selfrefering hierarchical source and a flat source with EntityFramework 6 and Database First Approach.
DataModel:
Image: https://i.stack.imgur.com/LIb1v.png
Expected Treeview
I have two types of Items:
"Dimensions" are from a selfrefering Source (XELL_DIMENSION) and
hierarchical Items. unknown/open Leveldepth.
"Elements" are from a flat Source (XELL_ELEMENTS) a
Image: https://i.stack.imgur.com/QeFwe.png
Ok here's what I have achieved so far:
Mainwindow Class:
Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
Dim elementsContext As New XELLEntities()
Tree.DataContext = elementsContext.XELL_DIMENSION.Include("XELL_ELEMS").ToList()
Tree.ItemsSource = elementsContext.XELL_DIMENSION.Where(Function(y) y.DIMEN_PARENT_ID Is Nothing).ToList()
End Sub
XAML CODE:
<TreeView Name="Tree" HorizontalAlignment="Left" Height="187" Margin="10,10,0,0" VerticalAlignment="Top" Width="415">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local2:XELL_DIMENSION}" ItemsSource="{Binding DIM_ALL_NODE}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding DIMEN_ID}"/>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding DIMEN_BEZ_LONG}"/>
<ListBox Name="Listy" ItemsSource="{Binding XELL_ELEMS}" DisplayMemberPath="ELEM_BEZ_LONG" BorderBrush="Transparent" BorderThickness="0"/>
</StackPanel>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
Result:
Question:
So that's the closest I came and I used a Listbox for the Elements - but this
is no solution to me.
- How do I solve my Problem?
I am thankful for any CodeSnipped provided.
Eureka! -SOLVED
I had to use a Converter and a TemplateSelector.
XAML:
<TreeView Name="Tree" HorizontalAlignment="Left" Height="187" Margin="10,10,0,0" VerticalAlignment="Top" Width="415"> <TreeView.Resources>
<local3:LeafDataTemplateSelector x:Key="LeafDataTemplateSelector" />
<HierarchicalDataTemplate DataType="{x:Type local2:XELL_DIMENSION}" ItemTemplateSelector="{StaticResource LeafDataTemplateSelector}">
<HierarchicalDataTemplate.ItemsSource>
<MultiBinding Converter="{StaticResource SimpleFolderConverter}">
<Binding Path="DIM_ALL_NODE" />
<Binding Path="XELL_ELEMS" />
</MultiBinding>
</HierarchicalDataTemplate.ItemsSource>
<StackPanel Orientation="Horizontal">
<Image HorizontalAlignment="Center" Height="20" VerticalAlignment="Center" Width="20" Source="MVVM/VIEW/IMAGES/Nodes.png" Stretch="Uniform" />
<TextBlock Foreground="#FF3399FF" Text="{Binding DIMEN_BEZ_LONG}" FontWeight="Bold"/>
</StackPanel>
</HierarchicalDataTemplate> <HierarchicalDataTemplate x:Key="Dimension" DataType="{x:Type local2:XELL_DIMENSION}" ItemTemplateSelector="{StaticResource LeafDataTemplateSelector}">
<HierarchicalDataTemplate.ItemsSource>
<MultiBinding Converter="{StaticResource SimpleFolderConverter}">
<Binding Path="XELL_ELEMS" />
<Binding Path="DIM_ALL_NODE" />
</MultiBinding>
</HierarchicalDataTemplate.ItemsSource>
<StackPanel Height="25" Orientation="Horizontal" ToolTip="Installation File">
<Image HorizontalAlignment="Center" Height="20" VerticalAlignment="Center" Width="20" Source="MVVM/VIEW/IMAGES/Nodes.png" Stretch="Uniform" />
<TextBlock Foreground="#FF3399FF" Text="{Binding DIMEN_BEZ_LONG}" FontWeight="Bold"/>
</StackPanel>
</HierarchicalDataTemplate> <DataTemplate x:Key="Element" DataType="{x:Type local2:XELL_ELEMENT}">
<StackPanel Height="25" Orientation="Horizontal" ToolTip="Installation File">
<Image HorizontalAlignment="Center" Height="20" VerticalAlignment="Center" Width="20" Source="MVVM/VIEW/IMAGES/Shape57.png" Stretch="Uniform" />
<TextBlock Foreground="DarkGray" Text="{Binding ELEM_BEZ_LONG}" FontWeight="Normal" FontStyle="Italic"/>
</StackPanel>
</DataTemplate>
</TreeView.Resources>
</TreeView>
VB.NET - MainWindow
Public Sub New()
' Dieser Aufruf ist für den Designer erforderlich.
InitializeComponent()
' Fügen Sie Initialisierungen nach dem InitializeComponent()-Aufruf hinzu.
End Sub
Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
Dim elementsContext As New XELLEntities()
Tree.DataContext = elementsContext.XELL_DIMENSION.Include("XELL_ELEMS").ToList()
Tree.ItemsSource = elementsContext.XELL_DIMENSION.Where(Function(y) y.DIMEN_PARENT_ID Is Nothing).ToList()
End Sub
VB.NET TemplateSelection
Public Class LeafDataTemplateSelector
Inherits DataTemplateSelector
Public Overrides Function SelectTemplate(item As Object, container As DependencyObject) As DataTemplate
Dim element As FrameworkElement = TryCast(container, FrameworkElement)
If element IsNot Nothing AndAlso item IsNot Nothing Then
If TypeOf item Is XELL_DIMENSION Then
Return TryCast(element.FindResource("Dimension"), DataTemplate)
ElseIf TypeOf item Is XELL_ELEMENT Then
Return TryCast(element.FindResource("Element"), DataTemplate)
End If
End If
Return Nothing
End Function
End Class
VB.NET Hierarchy Converter
Class HierarchyConverter : Implements IValueConverter
Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object Implements IValueConverter.Convert
Dim node = TryCast(value, Employee)
If node IsNot Nothing Then
Return node.Subordinates.Where(Function(i) i.ManagerID = node.EmployeeID).ToList()
Else
End If
End Function
Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object Implements IValueConverter.ConvertBack
Throw New NotSupportedException
End Function End Class
Hope this helps somebody ;)
I have created a simple datagrid to show some values, let the user change them and read back the changed values in the background program. Here is the XAML design file
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Button x:Name="ButAdd" Content="Add Row" HorizontalAlignment="Left" Height="23" Margin="362,34,0,0" VerticalAlignment="Top" Width="77"/>
<TextBox x:Name="TeBoResult" HorizontalAlignment="Left" Height="90" Margin="52,220,0,0" TextWrapping="Wrap" Text="Display the Row 0 colmn 1 changed value here:" VerticalAlignment="Top" Width="322" AcceptsReturn="True" IsManipulationEnabled="True"/>
<Button x:Name="ButRead" Content="Read Row 2" HorizontalAlignment="Left" Height="24" Margin="425,184,0,0" VerticalAlignment="Top" Width="82"/>
<DataGrid x:Name="DaGr" ItemsSource="{Binding}" HorizontalAlignment="Left" Height="133" Margin="10,75,0,0" VerticalAlignment="Top" Width="174" AutoGenerateColumns="False" IsManipulationEnabled="True" EnableColumnVirtualization="True" >
<DataGrid.Columns>
<DataGridTextColumn Header="Text" Binding="{Binding Path=No, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<DataGridCheckBoxColumn Header="Check box" Binding="{Binding Path=Sel}"/>
<DataGridComboBoxColumn Header="Combo box" Binding.XmlNamespaceManager="{Binding Path=Drop}"/>
</DataGrid.Columns>
</DataGrid>
<ComboBox x:Name="CoBo_IN" HorizontalAlignment="Left" Height="20" Margin="344,100,0,0" VerticalAlignment="Top" Width="84" Visibility="Visible">
<ComboBoxItem Content="Move" HorizontalAlignment="Left" Width="88"/>
<ComboBoxItem Content="Dock" HorizontalAlignment="Left" Width="88" Selected="ComboBoxItem_Selected"/>
</ComboBox>
</Grid>
And the backgroud vb.net code is here
Imports System.Collections.ObjectModel
Imports System.ComponentModel
Imports System.Runtime.CompilerServices
Imports DataGrid.datset
Imports System.Windows
Class MainWindow
Public Property coll As New ObservableCollection(Of bind)()
Private Sub ButAdd_Click(sender As Object, e As RoutedEventArgs) Handles ButAdd.Click
Dim qw As New bind()
qw.No = "Change Me"
qw.Sel = Nothing
qw.Drop = CoBo_IN
coll.Add(qw)
DaGr.ItemsSource = coll
End Sub
Private Sub ButRead_Click(sender As Object, e As RoutedEventArgs) Handles ButRead.Click
Dim val As String
For Each item As bind In DaGr.Items
val = item.No
TeBoResult.Text = TeBoResult.Text & val
Next
End Sub
End Class
Public Class datset : Implements INotifyPropertyChanged
Public Event PropertyChanged As PropertyChangedEventHandler _
Implements INotifyPropertyChanged.PropertyChanged
Private Sub NotifyPropertyChanged(<CallerMemberName()> Optional ByVal propertyName As String = Nothing)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
Public Structure bind
Public Property No As String
Public Property Sel As Boolean
Public Property Drop As ComboBox
End Structure
End Class
So When I click on the Add Row button the row with the default contents gets added in the TextColumn and CheckBoxColumn but the ComboBoxColumn dosent display the combo!!(but when I double click inside this cell the ComboBox appears but It is empty). What could be reason for this behaviour?
Next the user will change the contents inside the TextColumn and it gets changed in the GUI as required.
Next when the user clicks on the botton Read Row, all the contents of the TextColumn are read one after the other and is displayed in the Result text box. The problem is though the GUI has a new text when it is read sequentially the val variable still shows the previously bound values only. I thought the problem is with TwoWay binding but it seems to be something else.
Why does the read on DataGrid dosen't give an updated value?
I doesn't know to much of VB, but I can see some things here are not ok. For instance ItemsSource="{Binding}". Here you must to reference the collection to bind, in this case coll. The code should be something like this:
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
x:Name="window">
<Grid>
<Button x:Name="ButAdd" Content="Add Row" HorizontalAlignment="Left" Height="23" Margin="362,34,0,0" VerticalAlignment="Top" Width="77"/>
<TextBox x:Name="TeBoResult" HorizontalAlignment="Left" Height="90" Margin="52,220,0,0" TextWrapping="Wrap" Text="Display the Row 0 colmn 1 changed value here:" VerticalAlignment="Top" Width="322" AcceptsReturn="True" IsManipulationEnabled="True"/>
<Button x:Name="ButRead" Content="Read Row 2" HorizontalAlignment="Left" Height="24" Margin="425,184,0,0" VerticalAlignment="Top" Width="82"/>
<DataGrid x:Name="DaGr" ItemsSource="{Binding col, ElementName =window}" HorizontalAlignment="Left" Height="133" Margin="10,75,0,0" VerticalAlignment="Top" Width="174" AutoGenerateColumns="False" IsManipulationEnabled="True" EnableColumnVirtualization="True" >
<DataGrid.Columns>
<DataGridTextColumn Header="Text" Binding="{Binding Path=No, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<DataGridCheckBoxColumn Header="Check box" Binding="{Binding Path=Sel, Mode=TwoWay}"/>
<DataGridComboBoxColumn Header="Combo box" Binding.XmlNamespaceManager="{Binding Path=Drop, Mode=TwoWay"/>
</DataGrid.Columns>
</DataGrid>
<ComboBox x:Name="CoBo_IN" HorizontalAlignment="Left" Height="20" Margin="344,100,0,0" VerticalAlignment="Top" Width="84" Visibility="Visible">
<ComboBoxItem Content="Move" HorizontalAlignment="Left" Width="88"/>
<ComboBoxItem Content="Dock" HorizontalAlignment="Left" Width="88" Selected="ComboBoxItem_Selected"/>
</ComboBox>
</Grid>
Note the items source binding, and the name of the window, also note the bindings should be two way, for update the source too. This new binding references to the col collection. Now in he code behind you shoul not re-set the grid intems source each time you add an item. If the collection is an observable collection then it is added automatically, and now the items should works::
Private Sub ButAdd_Click(sender As Object, e As RoutedEventArgs) Handles ButAdd.Click
Dim qw As New bind()
qw.No = "Change Me"
qw.Sel = Nothing
qw.Drop = CoBo_IN
coll.Add(qw)
End Sub
I have this strange problem where I am unable to get an item from a ListBox. I have even tried to use the code from this site but it fails in my situation with a message: Unable to cast object of type 'System.Reflection.RuntimePropertyInfo' to type 'System.Windows.Controls.ListBoxItem'. The ListBox is binded to colors from XAML.
xmlns:sys="clr-namespace:System;assembly=mscorlib"
<Window.Resources>
<ObjectDataProvider MethodName="GetType"
ObjectType="{x:Type sys:Type}" x:Key="colorsTypeOdp">
<ObjectDataProvider.MethodParameters>
<sys:String>System.Windows.Media.Colors, PresentationCore,
Version=3.0.0.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35
</sys:String>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
<ObjectDataProvider ObjectInstance="{StaticResource colorsTypeOdp}"
MethodName="GetProperties" x:Key="colorPropertiesOdp">
</ObjectDataProvider>
</Window.Resources>
<!-- etc -->
<ListBox x:Name="ListBoxColor"
ItemsSource="{Binding Source={StaticResource colorPropertiesOdp}}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Auto"
Margin="5" Grid.RowSpan="5" SelectedIndex="113">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Rectangle Fill="{Binding Name}" Stroke="Black" Margin="2"
StrokeThickness="1" Height="20" Width="50"/>
<Label Content="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Private Sub ListBoxColor_SelectionChanged(sender As Object, _
e As SelectionChangedEventArgs) Handles ListBoxColor.SelectionChanged
Dim lbsender As ListBox
Dim li As ListBoxItem
lbsender = CType(sender, ListBox)
li = CType(lbsender.SelectedItem, ListBoxItem)
It breaks on the last line.
The items in your list box are of type System.Reflection.PropertyInfo. So you need to do something like this:
C#
if (ListBoxColor.SelectedItem != null)
{
var selectedItem = (PropertyInfo)ListBoxColor.SelectedItem;
var color = (Color)selectedItem.GetValue(null, null);
Debug.WriteLine(color.ToString());
}
VB.NET
If ListBoxColor.SelectedItem IsNot Nothing Then
Dim selectedItem As PropertyInfo = _
DirectCast(ListBoxColor.SelectedItem, PropertyInfo)
Dim color As Color = DirectCast(selectedItem.GetValue(Nothing, Nothing), Color)
Debug.WriteLine(color.ToString())
End If
The items in your ListBox are properties of the Colors class. You can't cast a property to a ListBoxItem because it isn't one.
Try calling ListBox.ContainerFromElement(lbsender.SelectedItem) instead.
MSDN Source: ContainerFromElement
I have a ItemsControl that's bound to an object, within the datatemplate of the ItemsControl i have two textblocks, I want to bind the first textblock's text property to another textblock that sits outside this ItemsControl.
I have tried finding the object in the parent datacontext and also simply trying to find the TextBlock with the Path=Text
one example is below :
<TextBlock Name="Name" Text="{Binding Name}"
Grid.Column="0"
FontSize="{DynamicResource SmallSize}"
TextWrapping="Wrap"
TextAlignment="Right"
Padding="4,0,0,0"
Grid.ColumnSpan="2" Background="Aqua"/>
<ItemsControl ItemsSource="{Binding TheValue}"
Padding="4,0,0,0"
Grid.Column="2"
HorizontalAlignment="Right">
<ItemsControl.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text = "{
Binding RelativeSource =
{RelativeSource FindAncestor,
AncestorType={x:Type Window}}, Path=Name}"
Grid.Column="0"
FontSize="{DynamicResource SmallSize}"
TextWrapping="Wrap" ........................
{Binding RelativeSource = {RelativeSource FindAncestor,
AncestorType={x:Type Window}}, Path=Name}
Here you say to WPF find first parent of this control with Type Window, e.g. it's "ParentWindow". After this binding occurs to "ParentWindow" Name property.
If you want enable binding to control, which defined in same XAML, you can set the source explicitly by using Binding.ElementName property.
This is example for you code:
<TextBlock Text = "{Binding ElementName=Name, Path=Text}"/>
By the way, using control name as "Name" not is good. If you use this control form code behind it's looking as Name.Text = "some text", which can cause a trouble to understand what is going on.
UPDATE:
Example of binding to control DataContext Property in different datatemplate
class MainViewModel
{
public Class1 C1 { get; set; }
public Class2 C2 { get; set; }
public MainViewModel()
{
C1 = new Class1 { S1 = "This is C1 data context" };
C2 = new Class2 { S2 = "This is C2 data context" };
}
}
In XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type local:MainViewModel}">
<StackPanel>
<ContentControl Name="cc1" Content="{Binding C1}"/>
<ContentControl Name="cc2" Content="{Binding C2}"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Class1}">
<TextBlock Text="{Binding S1}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Class2}">
<TextBlock Text="{Binding ElementName=cc1, Path=DataContext.C1.S1}"/>
</DataTemplate>
</Window.Resources>
<Grid>
<ContentControl Content="{Binding}"/>
</Grid>
</Window>
But, I don't think something like this is a good approach. Especially, because this can be many items with this DataTemplate. Maybe you need delegate this to your ViewModel.
I have a ListBox that each of its items has a button, I set all the textboxes in the dataitem that the Binding.UpdateSourceTrigger is Explicit.
I added a handler to the button's click, now what?
How do I collect the info from the controls? they don't have a key they are dynamic, how do I get their BindingExpressions?
<ListBox ItemsSource="{Binding Path=Phones}">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type data:Phone}">
<StackPanel Style="{StaticResource StackPanelStyle}">
<TextBox Margin="5" VerticalAlignment="Center" Name="tbNumber"
Text="{Binding Number, ValidatesOnExceptions=True, UpdateSourceTrigger=Explicit}"
/>
<Button Click="btnSavePhone_Click" Margin="5"
Content="_Update" IsEnabled="{Binding IsValid}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Private Sub btnSavePhone_Click(sender As Button, e As RoutedEventArgs)
'As I only have one TextBox I can use the following filter,
'you can of corse change it to Where c.Name = "tbNumber"
Dim tbNumber = From c As FrameworkElement In _
DirectCast(sender.Parent, StackPanel).Children Where TypeOf c Is TextBox
Dim x = tbNumber.ToList
Dim be = tbNumber.Cast(Of TextBox).First _
.GetBindingExpression(TextBox.TextProperty)
If Not be.HasError Then be.UpdateSource()
End Sub
Update
In some scenarios, BindingGroup would be the best solution, then U call BindingGroup.UpdateSources.