Visibility binding to ListboxItems inside template - c#

I'm trying to change the Visibility of elements inside a ListBoxItem by clicking on a CheckBox outside the ListBox which contains Items, but it doesn't seem to work.
It looks like the the binding doesn't work inside the ListBoxItems.
I'm using a template for my items
XAML
<UserControl.Resources>
<local:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
<DataTemplate x:Key="ShotTemplate">
<Grid x:Name="GridItem" Width="200">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox x:Name="ShotBox" Grid.Column="1" Text="{Binding Path=Description}" Visibility="{Binding EditMode, ElementName=EditMode, Converter={StaticResource BooleanToVisibilityConverter}}" />
<TextBlock x:Name="ShotBlock" Grid.Column="1" Text="{Binding Path=Description}" Visibility="{Binding EditMode, Converter={StaticResource BooleanToVisibilityConverter}, ConverterParameter=False }" />
</Grid>
</DataTemplate>
</UserControl.Resources>
<Grid>
<ListBox Name="ShotList" ItemsSource="{Binding AllShotsCollection}" ItemTemplate="{StaticResource ShotTemplate}"/>
<CheckBox Name="EditMode" IsChecked="{Binding EditMode}" Content="Edit Mode" HorizontalAlignment="Left" Margin="12,30,0,0" VerticalAlignment="Top"/>
</Grid>
ViewModel
private bool _editMode = true;
public bool EditMode
{
get { return _editMode; }
set { _editMode = value; RaisePropertyChanged("EditMode"); }
}
How do I change ShotBox and ShotBlock Visibility by checking or unchecking the CheckBox. I know the converter works correctly, that's not the problem it must have something to do with the binding.

The ElementName binding scope is within the template only. I would define an attached property on the ListView (not the ListViewItem) and have the Checkbox toggle that property. Within the DataTemplate, you'll be able to use RelativeSource / FindAncestor binding to find the ListView.

This line of xaml code is not working. You are trying to bind to the view element and viewmodel prop. at the same time.
<TextBox x:Name="ShotBox" Grid.Column="1" Text="{Binding Path=Description}" Visibility="{Binding EditMode, ElementName=EditMode, Converter={StaticResource BooleanToVisibilityConverter}}" />
Remove this 'ElementName=EditMode' so that it would bind properly to the viewmodel property 'EditMode'.
OR if you want to bind to the view element only use
Visibility="{Binding Path=IsChecked, ElementName=EditMode, Converter={StaticResource BooleanToVisibilityConverter}}" />

Related

Multiselect listbox with checkbox in WPF

I have a WPF project and I would like to have a listbox with checkbox on each ListboxItem and have an ObservableCollection to store checked ListboxItems.
I need to move checked chars from one ObservableCollection to another ObservableCollection.
With this code I can select multiple checkboxes but when I trigger the command MoveChar (command to move chars to another ObservableCollection) via the button only one ListboxItem moves and I need to click it more times to move all the checked chars.
View
<ListBox SelectionMode="Multiple" ItemsSource="{Binding Chars}" SelectedIndex="{Binding SelectedCharsIndex, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedItem="{Binding SelectedChars, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Char}" HorizontalAlignment="Center" />
<TextBlock Text="{Binding Description}" Grid.Column="1" HorizontalAlignment="Left"/>
<CheckBox Grid.Column="2" IsChecked="{Binding RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}, Path=IsSelected}" HorizontalAlignment="Right" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
ViewModel
public CharModel SelectedChars {
get { return _selectedChars; }
set { _selectedChars = value; NotifyPropertyChanged(); }
}
MoveChar = new RelayCommand(
() =>
{
if (SelectedChars != null)
{
TestChars.Add(SelectedChars);
Chars.Remove(SelectedChars);
Selected = false;
}
});
When I change SelectedChars to ObservableCollection<CharModel> it doesn´t work at all. In ViewModel I iterate each item via foreach.
You are binding SelectedChars to the SelectedItem property which is for use with Single selection listboxes and will only return a single object, not a collection. Try binding to the SelectedItems property. Be sure the "Items" part is plural. You will need to refactor the property type to be a SelectedObjectCollection as well but this inherits IList so you can iterate the same.

How to use DataTemplateSelector with ContentControl to display different controls based on the view-model?

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 Data​Template.​Data​Type:
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.

How to bind properties of one ObservableCollection of ListView to properties of SelectedItem of another ListView?

So I have a few ListViews. The first is binded to ObservaleCollection<ComPort>. All properties of ComPort may take some predefined values. Other ListViews are responsible for that properties: they show all that possible (predefined) values and SelectedItem should be the current value of that property of ComPort from the first ObservaleCollection.
I can't attach images so here is an external picture, it would make the situation clean: http://i.stack.imgur.com/ZBRRx.png
<Window.Resources>
<ResourceDictionary x:Name="rd">
<l:ComPorts x:Key="vComPorts"/>
<l:SystemPorts x:Key="vSystemPorts"/>
<l:BaudRates x:Key="vBaudRate"/>
<l:Parities x:Key="vParities"/>
<l:DataBits x:Key="vDataBits"/>
<l:StopBits x:Key="vStopBits"/>
<l:Timeouts x:Key="vTimeouts"/>
<l:ComPort x:Key="vSelectedPort"/>
</ResourceDictionary>
</Window.Resources>
...
<ListView
Name="PortsList"
Grid.Row="1"
Grid.Column="0"
Margin="5"
VerticalAlignment="Stretch"
ItemsSource="{StaticResource vComPorts}"
DataContext="{StaticResource vComPorts}"
SelectedValuePath="PortName"
SelectedValue="{Binding ElementName=SystemPortsList, Path=SelectedItem.Value}"
SelectionChanged="PortsList_SelectionChanged"
MouseDoubleClick="PortsList_MouseDoubleClick">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox />
<TextBlock Margin="5,0,0,0" Text="{Binding Path=Name}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ListView
x:Name="SystemPortsList"
Margin="5"
VerticalAlignment="Stretch"
DataContext="{Binding Source={StaticResource vSelectedPort}}"
ItemsSource="{Binding Source={StaticResource vSystemPortsView}}"
SelectedItem="{Binding Source={StaticResource vSelectedPort}, Path=PortName}"
MouseEnter="SystemPortsList_Refresh"
MouseLeave="SystemPortsList_Refresh"
Grid.Row="1"
Grid.Column="1" SelectionChanged="SystemPortsList_SelectionChanged">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Name="tb" Margin="5,0,0,0" Text="{Binding Path=Name}" />
</StackPanel>
</ListView.ItemTemplate>
</ListView>
I've tried to make an instance of class ComPort for saving current value of selected item from the first ListView, but anyway I can't cope with it without help. How this task should be solved?
1) Instead of handling SelectionChanged on the PortsList ListView, bind your checkbox to the ListViewItemsPanel like so:
<CheckBox IsChecked={Binding IsSelected, RelativeSource=Parent/>
2) Add an x:Name to your first ListBox, say x:Name="ComPortLB";
3) Remove DataContext on SystemPortsList;
4) Fix SelectedItem on SystemPortsList like so:
SelectedValue="{Binding ElementName=ComPortLB, Path=SelectedValue.PortName}"
I haven't tested any of this code and I haven't done this kind of stuff for a while, so I apologize for errors, but it should get you closer. I've also had to make some assumptions about your classes since you don't provide enough information.

DependencyProperty Binding Does Not Update To Collection CurrentItem

I am trying to bind a dependency property to a collection's current selection and for reasons I can't seem to grasp, the binding does not update when the collection changes.
In the example below, I show two example. One is updating correctly (on the textblock/run), and the other only displays the initial element and doesn't change when the data grid selection changes.
<Grid>
<Grid.Resources>
<CollectionViewSource Source="{Binding Path=List}" x:Key="myViewModel"/>
<my:UpdateNotWorking MyObjModel="{Binding Source={StaticResource myViewModel}, Path=CurrentItem}" x:Key="updateNotWorking" />
</Grid.Resources>
<DataGrid ItemsSource="{Binding Source={StaticResource myViewModel}}" Name="mylistbox"/>
<TextBlock TextWrapping="Wrap" FontWeight="Bold" Foreground="#FF50CEFF" FontSize="24" TextAlignment="Center" Height="75">
<Run Text="{Binding Source={StaticResource myViewModel}, Path=text}" Foreground="#FF00E200" />
</TextBlock>
<TextBox Text="{Binding Source={StaticResource updateNotWorking}, Path=MyObjModel.text}" Height="22"/>
</Grid>
My dependency property in this example is "MyObjModel" on the "UpdateNotWorking" dependency object which is instantiated from the xaml code.
I would appreciate any information as to why my property is not updating correctly.
Example Project
Paste this XAML into your MainWindow.
<Grid>
<Grid.Resources>
<CollectionViewSource Source="{Binding Path=List}" x:Key="myViewModel" />
<my:UpdateNotWorking x:Key="updateNotWorking" />
</Grid.Resources>
<DataGrid ItemsSource="{Binding Source={StaticResource myViewModel}}" Name="mylistbox"
SelectedItem="{Binding Source={StaticResource updateNotWorking}, Path=MyObjModel, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock TextWrapping="Wrap" FontWeight="Bold" Foreground="#FF50CEFF" FontSize="24" TextAlignment="Center"
Height="75">
<Run Text="{Binding Source={StaticResource myViewModel}, Path=text}" Foreground="#FF00E200" />
</TextBlock>
<TextBox Text="{Binding Source={StaticResource updateNotWorking}, Path=MyObjModel.text, UpdateSourceTrigger=PropertyChanged}"
Height="22" />
</Grid>
What it is now doing is setting updateNotWorking's MyObjModel property based on the DataGrid's SelectedValue, with UpdatePropertyTrigger set to PropertyChanged to see the changes immediately. No longer do we need to define updateNotWorking's property through the List's CurrentItem because it is not going to change just by selecting it with a DataGrid. You can keep it set, but it is not required as we are doing all the manual labor with the DataGrid SelectedValue.

Data binding for two textbox

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).

Categories