I am having a hard time trying to figure out how to bind in xaml a nested Observable Collection. The PLC class contains Tags. This might be familiar if you work in Automation. I have marked the areas of code I am having trouble with by saying "!Can't Figure this out". I am new to xaml and trying to do the binding in the xaml. If it can't be done in the xaml, a code behind solution would be helpful.
PLC Class
public class PLC
{
public string Name { get; set; }
public ObservableCollection<Tag> Tags { get; set; }
public PLC(string name)
{
Name = name;
Tags = new ObservableCollection<Tag>();
}
public override string ToString()
{
return Name;
}
}
Tag Class The PLC's tags when you click on a PLC the ListView to the right will get the tags associated with that PLC.
public class Tag
{
public Tag(string name, int value)
{
Name = name;
Value = value;
}
public string Name { get; set; }
public int Value { get; set; }
}
xaml - note this is a user control binded to the parent's viewmodel.
<UserControl x:Class="Test.UserControls.RuntimeControl"
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="450" d:DesignWidth="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="20"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<TextBlock Foreground="Red" Margin="10,0,0,0" >Runtime</TextBlock>
</Grid>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<ListView x:Name="PLCLV" Grid.Column="0" Margin="10" FontSize="25" SelectionMode="Single" ScrollViewer.VerticalScrollBarVisibility="Visible"
BorderThickness="0" ItemsSource="{Binding PLCs}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"></TextBlock>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ListView Grid.Column="1" Margin="10" FontSize="25" AlternationCount="2" ScrollViewer.VerticalScrollBarVisibility="Visible"
BorderThickness="0 " ItemsSource=***!Can't Figure this out!***>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Focusable" Value="false"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock>
<Run Text = "Name: "/>
<Run Text =***!Can't Figure this out!***
<Run Text ="Value: "/>
<Run Text =***!Can't Figure this out!***
</TextBlock>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Grid>
</UserControl>
FIX in the UserControl xaml Answer Marked as accepted
<ListView Grid.Column="1" Margin="10" FontSize="25"
DataContext="{Binding SelectedItem, ElementName=PLCLV}"
ItemsSource="{Binding Tags}">
First: you set the ItemsSource by list of PLC on "PLCLV" named ListView , so the type of object in SelectedItem of this ListView must be PLC type (or null), you can make this SelectedItem be the source of Tag list.
<ListView Grid.Column="1" Margin="10" FontSize="25" AlternationCount="2" ScrollViewer.VerticalScrollBarVisibility="Visible"
BorderThickness="0 " ItemsSource="{Binding SelectedItem.Tags, ElementName=PLCLV}">
<!-- Or SelectedItem.(local:PLC.Tags), the "(local:PLC.Tags)" means "the speicified property of specified type on unspecified type boxed property" -->
<!-- you can use Binding.ElementName to find the sepcified "Name/x:Name" named element in visual tree to be the Binding source -->
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Focusable" Value="false"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<!-- "x:Type" is a speicial markup for return a specific "Type" object -->
<!-- Set DataType of DataTemplate can help XAML editor show IntelliSense to help you -->
<!-- "local:" is a prefix of namespace for the type in xml, defined by "xmlns:", here make the editor know the this DataTemplate is apply on the "Tag" type -->
<DataTemplate DataType="{x:Type local:Tag}">
<TextBlock>
<Run Text="Name: "/>
<Run Text="{Binding Name}" />
<Run Text="Value: "/>
<Run Text="{Binding Value}" />
</TextBlock>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Or, use DataContext and ItemsSource:
<ListView Grid.Column="1" Margin="10" FontSize="25" AlternationCount="2" ScrollViewer.VerticalScrollBarVisibility="Visible"
BorderThickness="0"
DataContext="{Binding SelectedItem, ElementName=PLCLV}"
ItemsSource="{Binding Tags}">
<!-- Or ItemsSource="{Binding Path=(local:PLC.Tags)}" -->
...
Assuming the DataContext of the UserControl is set correctly (i.e. the binding of PLCs is correct)
Add this property in code behind under PLCs collection property (probably you will need it later in code behind)
private PLC _selectedPlc;
public PLC SelectedPlc
{
get => _selectedPlc;
set
{
_selectedPlc = value;
OnPropertyChanged(nameof(SelectedPlc)); // this will update the second ListView
}
}
public ObservableCollection PLCs {set; get;} // you have this already
The first ListView can see all PLCs.. Let's bind the DataContext of the second ListView with SelectedPlc property, this way, the second ListView will only see one PLC.
<ListView x:Name="PLCLV" Grid.Column="0" Margin="10" FontSize="25" SelectionMode="Single" ScrollViewer.VerticalScrollBarVisibility="Visible"
BorderThickness="0" ItemsSource="{Binding PLCs}"
SelectedItem="{Binding SelectedPlc}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" Foreground="{Binding ForegroundColor}"></TextBlock>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ListView Grid.Column="1" Margin="10" FontSize="25" AlternationCount="2" ScrollViewer.VerticalScrollBarVisibility="Visible"
BorderThickness="0"
DataContext="{Binding SelectedPlc}"
ItemsSource="{Binding Tags}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Focusable" Value="false"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock>
<Run Text= "Name: "/>
<Run Text="{Binding Name}"
<Run Text="Value: "/>
<Run Text="{Binding Value}"
</TextBlock>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Do not forget to fill PLCs (and Tags inside each PLC) collections with some data!
Note: there is ForegroundColor property missing in your PLC class!
Related
I am working with MVVM design pattern where I want to change the type of control to be used based on the type of item source object.
My code for item source types :
public class PropertyItemVM : ViewModelBase
{
// Some base code for all basic property items
}
public class StringValuePropertyItemVM : PropertyItemVM
{
// Code to correctly create string value property item
}
public class ComboBoxPropertyItemVM : PropertyItemVM
{
// Code to correctly create combo box property item
}
In my view model I have property list binded to xaml as
public ObservableCollection<PropertyItemVM> Properties { get; set; }
This collection contains both the types of objects StringValuePropertyItemVM and ComboBoxPropertyItemVM.
Now what I want to achieve is, based on type of object I want to decide whether the xaml will contain a TextBox or a ComboBox.
My xaml code :
<StackPanel>
<!--Items Control containing all list of properties to be shown-->
<ItemsControl x:Name="Local" ItemsSource="{Binding Properties}" Background="White">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<Label Content="{Binding Label}"
IsEnabled="{Binding IsEnabled}"
Grid.Column="0"/>
<!-- Here based on type need to change the TextBox with ComboBox-->
<TextBox Text="{Binding Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Grid.Column="1"
IsEnabled="{Binding IsEnabled}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
With some help I found out I can have different data templates for both types and switch among those, however I can't find how to do that. Can anyone help me with how can this be achieved ?
Automatic Data Template Selection
Move the data templates to the ItemsControl resources and assign a DataType.
<StackPanel>
<!--Items Control containing all list of properties to be shown-->
<ItemsControl x:Name="Local" ItemsSource="{Binding Properties}" Background="White">
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type local:StringValuePropertyItemVM}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<Label Content="{Binding Label}"
IsEnabled="{Binding IsEnabled}"
Grid.Column="0"/>
<!-- Here based on type need to change the TextBox with ComboBox-->
<TextBox Text="{Binding Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Grid.Column="1"
IsEnabled="{Binding IsEnabled}"/>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type local:ComboBoxPropertyItemVM}">
<ComboBox/>
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
</StackPanel>
The ItemsControl will automatically select the right data template based on the type.
Data Template Selector
Although you do not need it in this case, for more complex cases (e.g. conditions based on the view model), you could implement a DataTemplateSelector, like this:
public class PropertyDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (!(container is FrameworkElement frameworkElement))
return null;
switch (item)
{
case StringValuePropertyItemVM _:
return (DataTemplate)frameworkElement.FindResource("StringValuePropertyItemVMDataTemplate");
case ComboBoxPropertyItemVM _:
return (DataTemplate)frameworkElement.FindResource("ComboBoxPropertyItemVMDataTemplate");
default:
return null;
}
}
}
Then you would define the data templates in a resource dictionary in scope with keys.
<DataTemplate x:Key="StringValuePropertyItemVMDataTemplate" DataType="{x:Type local:StringValuePropertyItemVM}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<Label Content="{Binding Label}"
IsEnabled="{Binding IsEnabled}"
Grid.Column="0"/>
<!-- Here based on type need to change the TextBox with ComboBox-->
<TextBox Text="{Binding Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Grid.Column="1"
IsEnabled="{Binding IsEnabled}"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="ComboBoxPropertyItemVMDataTemplate" DataType="{x:Type local:ComboBoxPropertyItemVM}">
<ComboBox/>
</DataTemplate>
Finally, you would assign or reference an instance of the selector to the ItemsControl.
<StackPanel>
<!--Items Control containing all list of properties to be shown-->
<ItemsControl x:Name="Local" ItemsSource="{Binding Properties}" Background="White">
<ItemsControl.ItemTemplateSelector>
<local:PropertyDataTemplateSelector/>
</ItemsControl.ItemTemplateSelector>
</ItemsControl>
</StackPanel>
To split into group i have the following code in xaml:
<ListView x:Name="lvPallets" ItemsSource="{Binding Entries}">
<ListView.ItemTemplate>
<DataTemplate>
<WrapPanel>
<Image Source="{Binding Image}"/>
<TextBlock Text="{Binding Name}" Margin="5,5,5,5" VerticalAlignment="Center" />
</WrapPanel>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock FontWeight="Bold" FontSize="14" Text="{Binding Name}"/>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
To actually split into groups i added x:Name attribute and some code in code behind file:
public SimulationWindow()
{
InitializeComponent();
_viewModel = new SimulationWindowViewModel();
DataContext = _viewModel;
lvPallets.Loaded += foo;
}
void foo(object sender, EventArgs e)
{
CollectionView view = (CollectionView)CollectionViewSource.GetDefaultView(lvPallets.ItemsSource);
PropertyGroupDescription groupDescription = new PropertyGroupDescription("Group");
view.GroupDescriptions.Add(groupDescription);
}
I would like to remove x:Name attribute and function foo. How can i do it?
It is possible to define a CollectionViewSource in the XAML, simply define it in the Resources of the parent and use it via x:Key.
<!-- Define CollectionViewSource -->
<Parent.Resources>
<CollectionViewSource x:Key="GroupCVS" Source="{Binding Entries}">
<!-- Group by the Property 'Group' -->
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Group" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</Parent.Resources>
<!-- use the CollectionViewSource -->
<ListView ItemsSource="{Binding Source={StaticResource GroupCVS}}">
Note: I haven't found a way to declare the CollectionViewSource directly in the ListView due it must be known before applied (-> StaticResource).
I want to create a simple window that would display different controls (SpinEdit or TextEdit) based on the view-model that is selected.
I have the code and logic behind it done already, what is left is displaying the control (SpinEdit or TextEdit) itself.
XAML:
<dx:DXWindow.Resources>
<DataTemplate x:Key="DataTemplate_Value">
<dxe:SpinEdit Height="23" MinWidth="200" Width="Auto"
Text="{Binding Path=Value, Mode=TwoWay}"
Mask="{Binding Mask, Mode=OneWay}"
MaxLength="{Binding Path=InputLength}" />
</DataTemplate>
<DataTemplate x:Key="DataTemplate_Text">
<dxe:TextEdit Height="23" MinWidth="200" Width="Auto"
Text="{Binding Path=Value, Mode=TwoWay}"
MaskType="RegEx" Mask="{Binding Mask, Mode=OneWay}"
MaxLength="{Binding Path=InputLength}"/>
</DataTemplate>
<local:PropertyDataTemplateSelector x:Key="templateSelector"
DataTemplate_Value="{StaticResource DataTemplate_Value}"
DataTemplate_Text="{StaticResource DataTemplate_Text}" />
</dx:DXWindow.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" >
<Label x:Uid="Label" MinHeight="24" MinWidth="60" Content="Value" />
<ContentControl ContentTemplateSelector="{StaticResource templateSelector}" />
</StackPanel>
<StackPanel Grid.Row="1" x:Uid="OKCancel_Buttons" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Bottom">
<Button Height="23" x:Name="OK_Button" Click="OK_Click" Content="OK" IsDefault="True" HorizontalAlignment="Right" MinWidth="95" />
<Button Height="23" x:Name="Cancel_Button" Click="Cancel_Click" Content="Cancel" HorizontalAlignment="Right" MinWidth="95" />
</StackPanel>
Where in the <ContentControl> I want to select which control will be displayed (SpinEdit for numbers and TextEdit for names/letters)
C#:
public class PropertyDataTemplateSelector : DataTemplateSelector
{
public DataTemplate DataTemplate_Value { get; set; }
public DataTemplate DataTemplate_Text { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var selector = item as TInputBaseVM;
if(selector is TInputValueVM)
return DataTemplate_Value;
return DataTemplate_Text;
}
}
Where I want to return a specific DataTemplate based on the view-model that is created in the c++/cli code.
C++/cli:
TInputValueVM ^oExchange_Value;
TInputTextVM ^oExchange_Text;
int inputFormat = A_Attributes.GetInputFormat();
if(inputFormat)
oExchange_Text = gcnew TInputTextVM(gcnew System::String(A_Attributes.GetTitle()), gcnew System::String(A_Attributes.GetMask()),
A_Attributes.GetInputLength(), gcnew System::String(A_Attributes.GetInitialText()));
else
oExchange_Value = gcnew TInputValueVM(gcnew System::String(A_Attributes.GetTitle()), gcnew System::String(A_Attributes.GetMask()),
A_Attributes.GetInputLength(), A_Attributes.GetInitialValue());
Dialogs::TSignalNumberPositionDialog^ dialog = gcnew Dialogs::TSignalNumberPositionDialog();
if(inputFormat)
dialog->DataContext = oExchange_Text;
else
dialog->DataContext = oExchange_Value;
dialog->ShowDialog();
The point is, the item value in the overriden selector function always has the null value and I have no idea how to bind it in XAML since all the examples I've managed to find so far are ListBoxes etc. There's no example on how to display different controls based on the view-model.
EDIT:
As suggested, I added Content property in the ContentControl and passed it an argument which is now the 'item' argument in the selector. Works just fine!
You do not need a DataTemplateSelector. WPF provides a mechanism that automatically selects a DataTemplate for the ContentTemplate of a ContentControl according to the type of a Content.
As explained in DataTemplate.DataType:
When you set this property to the data type without specifying an x:Key, the DataTemplate gets applied automatically to data objects of that type.
So drop the x:Key value and your DataTemplateSelector, set DataType
<dx:DXWindow.Resources>
<DataTemplate DataType="{x:Type local:TInputValueVM}">
<dxe:SpinEdit Height="23" MinWidth="200" Width="Auto"
Text="{Binding Path=Value, Mode=TwoWay}"
Mask="{Binding Mask, Mode=OneWay}"
MaxLength="{Binding Path=InputLength}" />
</DataTemplate>
<DataTemplate DataType="{x:Type local:TInputTextVM}">
<dxe:TextEdit Height="23" MinWidth="200" Width="Auto"
Text="{Binding Path=Value, Mode=TwoWay}"
MaskType="RegEx" Mask="{Binding Mask, Mode=OneWay}"
MaxLength="{Binding Path=InputLength}"/>
</DataTemplate>
</dx:DXWindow.Resources>
and bind the ContentControl's Content to a property that returns either a TInputValueVM or a TInputTextVM:
<ContentControl Content="{Binding InputVM}" />
The appropriate DataTemplate will now be selected automatically.
You have to add some value in the Content property of the ContentControl. That value will be passed to the SelectTemplate as the object item. You probably should bind to it some property in your ViewModel to be able to change that from there.
I have The following structure
QPage class Object contains a List<List<QLine>>
QLine object contains List<Qword>
every list of words constructs a line and every list of lines consists a group(paragraph) and every list of paragraphs consists a page.
I want to bind the page to structure like this in XAML
<ListView>
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBlock>
</TextBlock>
</StackPanel>
</StackPanel>
</ListView>
where each item of the ListView is a paragraph(List<QLine>) and each vertical stack panel holds an item of the List<QLine> and each item of the horizontal stack panel holds an item of the List<Qword> and the texblock is bound to Qword.text property. I have no idea how to do such binding from the XAML code.
Hopefully I did not miss some list but this should work. Basically it's a ListBox that hosts List<List<QLine>> (called it QPageList). Then you have ItemsControl that hosts each List<QLine> in vertical panel and finally there is another ItemsControl that hosts List<Qword> from QLine (called it QwordList) where each QWord is displayed as TextBlock on horizontal StackPanel
<!-- ItemsSource: List<List<QLine>>, Item: List<QLine> -->
<ListBox ItemsSource="{Binding QPageList}">
<ListBox.ItemTemplate>
<DataTemplate>
<!-- ItemsSource: List<QLine>, Item: QLine -->
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<!-- ItemsSource: QLine.List<QWord>, Item: QWord -->
<ItemsControl ItemsSource="{Binding QwordList}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<!-- Item: QWord -->
<TextBlock Text="{Binding text}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
What you are looking for is ListView.ItemTemplate. Basically, you need to provide your list with a way to understand the nested data structure of your rows.
Here is a good tutorial to get you started on ItemTemplates.
Once your list has an item template then you just bind the ListView directly to your data source and that's it.
Hopefuly this will prove helpful. Taken from here
Authors (list)
- - Name
- - Books (list)
| - - Title
| - - Contents
Sample code :
<Window.Resources>
<DataTemplate x:Key="ItemTemplate1">
<StackPanel>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="ItemTemplate2">
<StackPanel>
<TextBlock Text="{Binding Title}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid x:Name="LayoutRoot" DataContext="{Binding Source={StaticResource AuthorDataSource}}">
<ListBox x:Name="AuthorList" ItemTemplate="{DynamicResource ItemTemplate1}" ItemsSource="{Binding Authors }" >
<ListBox x:Name="BookList" DataContext="{Binding SelectedItem, ElementName=AuthorList}" ItemsSource="{Binding Books }" ItemTemplate="{DynamicResource ItemTemplate2}" />
<TextBlock x:Name="BookContent" DataContext="{Binding SelectedItem, ElementName=BookList}" Text="{Binding Contents }" />
</Grid>
Forward to what Alex is saying, it'd be a good idea to Encapsulate (If that's the correct word here) the object within classes, i.e.:
public class Page
{
public List<Paragraph> Paragraphs { get; set; }
}
public class Paragraph
{
public List<QWord> Sentences { get; set; }
}
public class Sentence
{
public List<QWord> Words { get; set; }
}
That'd help when you bind data in your XAML, i.e. if you look at the tutorial which Alex provided:
<TextBlock Text="Name: " />
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
<TextBlock Text="{Binding Age}" FontWeight="Bold" />
<TextBlock Text=" (" />
<TextBlock Text="{Binding Mail}" TextDecorations="Underline" Foreground="Blue" Cursor="Hand" />
<TextBlock Text=")" />
Hope that helps you.
I have 2 textbox, each in a different listview. First textbox is supposed to show data from a xml file. So when i click on the textbox, the data in the first textbox will show on the second textbox. I did this by doing a very big round about, getting the specific object when i click it and append to another listview. Is there a shorter way to do this through binding by element name in the xaml? My elementName in textbox1 will be the name for textbox2. I try doing it, but I am not sure what my path should be?
Sorry for not including my xaml.
<Window x:Class="GridViewTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
xmlns:local="clr-namespace:GridViewTest"
Title="MainWindow" mc:Ignorable="d" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" d:DesignHeight="541" d:DesignWidth="858" SizeToContent="WidthAndHeight">
<Window.Resources>
<local:PacketList x:Key="PacketList"/>
<local:BindableSelectionTextBox x:Key="BindableSelectionTextBox"/>
</Window.Resources>
<Grid Height="500" Width="798">
<Grid.RowDefinitions>
<RowDefinition Height="142*" />
<RowDefinition Height="145*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="234*" />
<ColumnDefinition Width="233*" />
</Grid.ColumnDefinitions>
<ListView ItemsSource="{Binding}" x:Name="lvItems" Grid.RowSpan="2" Grid.ColumnSpan="2">
<ListView.View>
<GridView AllowsColumnReorder="True">
<GridViewColumn Header="Header" Width="200">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid>
<TextBox Name ="A" Tag="Header" Text="{Binding SelectedText, Path=headerObj.headervalue}" PreviewMouseLeftButtonUp="Handle_Click"
IsReadOnly="True" BorderThickness="0" >
</TextBox>
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
<ListView Margin="0,245,0,8" Grid.ColumnSpan="2" Grid.RowSpan="2" >
<TextBox Name="headText" Text="{Binding SelectedText,ElementName=A}"/>
</ListView>
</Grid>
Firstly let us have some education on NameScoping in WPF. In WPF any Bindings within Templates are scoped to that Template only. Also any element named within a template wont be available for Binding.ElementName reference outside the template.
So in your case TextBox A cannot be referred by TextBox headText as textbox A is name-scoped under GridViewColumn.CellTemplate.
Also why is headText textbox under a ListView? ItemsControls like ListBox, ListView, DataGrid should not be used as panels or containers to host single elements. Their intention is to show multiple items. Use Panels or ContentControl instead.
<Grid Margin="0,245,0,8" Grid.ColumnSpan="2" Grid.RowSpan="2" >
<TextBox Name="headText" Text="{Binding SelectedText,ElementName=A}"/>
</Grid>
OR
<ContentControl Margin="0,245,0,8" Grid.ColumnSpan="2" Grid.RowSpan="2" >
<TextBox Name="headText" Text="{Binding SelectedText,ElementName=A}"/>
</ContentControl>
Now to synchronize selection between two textboxes use the following trick...
XAML
<TextBox Name="SelectionSource"
Tag="{Binding ElementName=SelectionTarget}"
SelectionChanged="SelectionSource_SelectionChanged" />
<TextBox Name="SelectionTarget"
Text="{Binding SelectedText, ElementName=SelectionSource,
Mode=TwoWay, UpdateSourceTrigger=Explicit}" />
Code Behind ...
private void SelectionSource_SelectionChanged(object sender, RoutedEventArgs e)
{
var targetTextBox = ((TextBox) sender).Tag as TextBox;
if (targetTextBox != null)
{
var bndExp
= BindingOperations.GetBindingExpression(
targetTextBox, TextBox.TextProperty);
if (bndExp != null)
{
bndExp.UpdateTarget();
}
}
}
If you are using MVVM then handle this SelectionSource_SelectionChanged event in attached behavior.
EDIT 2:
Now in case if one text box is part of ListBox template and other is outside the template then use content control hack...
XAML:
<Window.Resources>
<TextBox x:Key="SelectionTarget"
Text="{Binding Tag.SelectedText,
RelativeSource={RelativeSource Self},
Mode=TwoWay,
UpdateSourceTrigger=Explicit}" />
</Window.Resources>
<StackPanel>
<ListBox>
<ListBox.ItemsSource>
<x:Array Type="{x:Type System:String}">
<System:String>Test String 1</System:String>
<System:String>Test String 2</System:String>
<System:String>Test String 3</System:String>
</x:Array>
</ListBox.ItemsSource>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Name="SelectionSource"
Text="{Binding Path=., Mode=TwoWay}"
Tag="{StaticResource SelectionTarget}"
SelectionChanged="SelectionSource_SelectionChanged" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ContentControl Content="{StaticResource SelectionTarget}">
</ContentControl>
</StackPanel>
Code Behind
private void SelectionSource_SelectionChanged(
object sender, RoutedEventArgs e)
{
var targetTextBox
= ((TextBox) sender).Tag as TextBox;
if (targetTextBox != null)
{
targetTextBox.Tag = (TextBox) sender;
var bndExp
= BindingOperations.GetBindingExpression(
targetTextBox, TextBox.TextProperty);
if (bndExp != null)
{
bndExp.UpdateTarget();
}
}
}
Hope this helps.
I'm not really sure what's going on with "SelectedText" you are trying to bind to, but if all you are trying to do is display the "lvItems" SelectedItem text in your "headText" TextBox the following should work
<TextBox Name="headText" Text="{Binding ElementName=lvItems, Path=SelectedItem.headerObj.headervalue}" />
You'll need to change your TextBox "A" binding as well.
<TextBox Name ="A" Tag="Header" Text="{Binding headerObj.headervalue}" IsReadOnly="True" BorderThickness="0" >
</TextBox>
Assuming that headerObj is a property of the Packet class, and headervalue is a property of that, and headervalue is the value you wish to bind to.
The text in "headText" will update when the SelectedItem is changed (not when the TextBox is clicked).