Change listbox template on lost focus event in WPF - wpf

I want to present a listbox with TextBlocks as items. When the user clicks/selects an item it changes into a TextBox for editing. As soon as the controls loses focus the item would turn back to a TextBlock.
The following XAML is almost working in that the TextBlock does turn into a TextBox when it's selected. It also turns back to a TextBlock if I select another item on the list. The problem is that if I move out of the listbox (in this case to the Add New text box) the list item stays as a TextBox.
The question ( WPF ListViewItem lost focus event - How to get at the event? ) looked promising but I can't make it work. I tried using the IsFocused property but then I wasn't able to edit the textbox because when I got into the textbox the listboxitem would go out of focus and thus turning back to a TextBlock before I had a chance to edit the text.
How can I make the TextBox turn back to TextBlock if the listbox/item loses the focus?
(Any other implementation that accomplishes the goal are also welcomed)
<Window x:Class="MyView.MainWindow1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
Title="MainWindow1" Height="300" Width="200">
<Window.Resources>
<DataTemplate x:Key="ItemTemplate">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
<DataTemplate x:Key="SelectedTemplate">
<TextBox Text="{Binding Name, Mode=TwoWay}" />
</DataTemplate>
<Style TargetType="{x:Type ListBoxItem}" x:Key="ContainerStyle">
<Setter Property="ContentTemplate" Value="{StaticResource ItemTemplate}" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="ContentTemplate" Value="{StaticResource SelectedTemplate}" />
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<StackPanel >
<ListBox ItemsSource="{Binding Departments}" HorizontalContentAlignment="Stretch"
ItemContainerStyle="{StaticResource ContainerStyle}" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox Text="{Binding NewDepartmentName, Mode=TwoWay}" />
<Button Grid.Column="1" Width="50" Content="Add" Command="{Binding Path=AddNewDepartmentCommand}" />
</Grid>
</StackPanel>
</Window>

This doesn't answer your question, but gives an alternative solution by making a textbox look like a textblock when the listboxitem isn't selected:
<Page.Resources>
<ResourceDictionary>
<Style x:Key="ListBoxSelectableTextBox" TargetType="{x:Type TextBox}">
<Setter Property="IsHitTestVisible" Value="False" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}, AncestorLevel=1}}" Value="True">
<Setter Property="IsHitTestVisible" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
</Page.Resources>
<Grid>
<ListBox ItemsSource="{Binding Departments}" HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Margin="5" Style="{StaticResource ListBoxSelectableTextBox}" Text="{Binding Name}" BorderBrush="{x:Null}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>

There are many ways you can do this:
Above answer
Create a TextBox style based on TextBoxBase such that when disabled, it makes the TextBox look like a TextBlock by setting the BorderThickness="0", Background="transparent"
Have both TextBlock and TextBox on each ListViewItem and using the above answer technique hide and show the TextBlock/TextBox accrodingly

Related

Something Like For Loop in XAML

I have a Resource Dictionary in which I want to have a common DataTemplate for ComboBox.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<DataTemplate DataType="{x:Type ComboBox}">
<StackPanel Orientation="Horizontal">
<!--Here I need to use something like For Loop-->
<TextBlock Text=""></TextBlock>
</StackPanel>
</DataTemplate>
</ResourceDictionary>
Now I have created a dependency property of type integer named NoOfColumns. While declaring the comboBox I need to set the NoOfColumns property to automatically generate that number of columns. I want them to databind.
Update as requested by Joe
<ComboBox x:Name="cbUnder" ItemsSource="{Binding GroupsAndCorrespondingEffects}"
IsEditable="True" SelectedItem="{Binding SelectedGroup, Mode=TwoWay}"
Text="{Binding InputValue, UpdateSourceTrigger=PropertyChanged}" TextSearch.TextPath="GroupName"
Grid.Column="1" Grid.ColumnSpan="4" Grid.Row="3">
<ComboBox.Resources>
<DataTemplate DataType="{x:Type vm:GroupAndCorrespondingEffect}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding GroupName}" Width="250">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding IsHighlighted}" Value="True">
<Setter Property="Foreground" Value="Blue" />
<Setter Property="FontWeight" Value="Bold"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock Text="{Binding CorrespondingEffect}" />
</StackPanel>
</DataTemplate>
</ComboBox.Resources>
</ComboBox>
There's nothing like for in XAML, but ItemsControl is very much like foreach. Instead of setting an int property, make an ObservableCollection<T> and add that many objects to it, and then bind the ItemsControl to your collection property.
This has the added benefit that each collection item can expose properties to be bound, e.g. if you wanted to display different text in each TextBlock, you could put a property on your collection item and bind the TextBlock to that property.

ComboBox with two columns in popup

I need to create a custom control containing a combobox whose popup will have the Name propeprty of the bound objects aligned to the left, and the CreatedDate property of the bound objects aligned to the right in each pop up item. Also the Name and the CreatedDate must not overlap. The Name of the object is of variable length
I tried solving this problem using a DataTemplate in the Combobox.ItemTemplate, inside the data template I have a grid with two columns aligned appropriately. The Grid's horizontal alignment is set to Stretch but for some reason the Grid doesn't fill out the available space in the popup. Does anyone know how to get around this and why it happens? I am using WPF 3.5.
<UserControl.Resources>
<CollectionViewSource x:Key="cvsProcessingSessionList" Source="{Binding ProcessingSessionList, ElementName=View}"/>
<Style TargetType="{x:Type ComboBox}"
BasedOn="{StaticResource {x:Static res:ObjectResources.LEComboBoxStyle}}">
<Setter Property="Margin" Value="5,3"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
</UserControl.Resources>
<GroupBox x:Name="grbProcessingSession">
<GroupBox.Header>
<Bold>Processing Session:</Bold>
</GroupBox.Header>
<GroupBox.Content>
<ComboBox
x:Name="ccbProcessingSessionName"
ItemsSource="{Binding Source={StaticResource cvsProcessingSessionList}}"
Loaded="OnLoaded" TextSearch.TextPath="Name"
IsEditable="True" MaxDropDownHeight="500" >
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid Background="Pink" HorizontalAlignment="Stretch" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" TextAlignment="Left" HorizontalAlignment="Left"
Margin="0,0,5,0" Text="{Binding Name}"/>
<TextBlock Grid.Column="1" TextAlignment="Right" HorizontalAlignment="Right"
Text="{Binding CreatedDate, StringFormat=dd/MM/yyyy}"/>
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</GroupBox.Content>
</GroupBox>
Just add this binding to your grid in yiour DataTemplate ...
Width="{Binding ActualWidth,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ComboBoxItem}},
Mode=OneTime}"
Also for better effect, apply the background color to the ComboBoxItem and not to the grid...
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="Background" Value="Pink"/>
</Style>
</ComboBox.ItemContainerStyle>
Whenever I want to do the same that you are asking I add:
<ComboBox ...>
<ComboBoxItem HorizontalContentAlignment="Stretch">
</ComboBoxItem>
</ComboBox>
The thing is that I place my custom DataTemplate inside the ComboBoxItem.
Maybe you should try it...
Actually, the better way is to set HorizontalContentAlignment="Stretch" at the ComboBox level. This will work even if width of ComboBox changes (e.g. if it is in resizable container).
However, be aware that this works in WPF and Silverlight 5 but don't work in some older versions of Silverlight.

wpf - ListBox - bind SelectedItem to an xml attribute?

I have a listbox on a usercontrol which is populated by a xml file.
<Machines xmlns="">
<Machine Name="Prod1" IP="192.168.1.200" isDefault="true" InstanceName="sql08" />
<Machine Name="Prod2" IP="192.168.1.101" />
<Machine Name="Test1" IP="192.168.1.103" />
<Machine Name="Test2" IP="192.168.1.104" />
</Machines>
I would like to bind the Listbox's Selected Item to the Machine which has a isDefault=true attribute.
My current xmldataprovider and ItemTemplate are listed below along with my ListBox markup. I was not sure if I needed to do some xpath binding in the datatemplate, or if I should make an explicit style with a trigger for this task? Or if either of those approaches would even work? One of the things I can't understand is how I can bind to an attribute that only exists on one node of my file.
<XmlDataProvider x:Key="DataList" Source="XML\ListboxSettings.xml" XPath="Machines/Machine"/>
<DataTemplate x:Key="MachineDataTemplate">
<TextBlock Text="{Binding XPath=#Name}" ToolTip="{Binding XPath=#IP}" />
</DataTemplate>
<ListBox Name="MerlinsListbox" Margin="5" Height="{Binding Height, ElementName=border}" Background="#FF252525" FontFamily="Consolas" FontSize="16" Foreground="#FFFBF9F9"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource MerlinDataTemplate}"
IsSynchronizedWithCurrentItem="true"/>
Two possible ways you could handle this are as follows:
1) You could set the ItemContainerStyle and bind the ListBoxItem's IsSelected property to the #isDefault attribute.
<ListBox Name="MerlinsListbox" Margin="5"
Background="#FF252525" FontFamily="Consolas" FontSize="16" Foreground="#FFFBF9F9"
ItemsSource="{Binding Source={StaticResource DataList}}"
ItemTemplate="{StaticResource MachineDataTemplate}"
IsSynchronizedWithCurrentItem="true">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding XPath=#isDefault, Mode=OneTime}"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
Or 2) add a trigger for the ItemContainerStyle:
<ListBox ...>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Style.Triggers>
<DataTrigger Binding="{Binding XPath=#isDefault}" Value="true">
<Setter Property="IsSelected" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>

WPF Tooltip Binding

I am only two weeks into WPF so this is probably a trivial question. I have a collection "CellList" which has a few properties I would like to bind to a ToolTip so when I hover over a label information from the current instance of CellList is displayed. How do I do that? I understand simple binding and this maybe simple binding too but I can't wrap my head around it. Below is my XAML for the label. Could someone explain to me how I can accomplish this.
<HierarchicalDataTemplate>
<ListBox ItemsSource="{Binding CellList}">
<ListBox.ItemTemplate>
<DataTemplate>
<Label Content=" " Height="20" Width="15" Background="{Binding Path=ExptNameBkg, Converter={StaticResource ExptNameToBrushConverter}}" BorderBrush="Black" BorderThickness="1" >
</Label>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</HierarchicalDataTemplate>
Thanks.
The tricky thing about ToolTips is that a ToolTip is an object you associate with a control, and not part of the control's visual tree. So you can't populate it the way you'd populate things in the visual tree, e.g.:
<TextBox.ToolTip>
<StackPanel>
...put bound controls here
</StackPanel>
</TextBox.ToolTip>
Instead, what you have to do is create a specific instance of a ToolTip, and assign it a style that sets its DataContext (very important; that's how you can bind to the properties of the data source of its "placement target," i.e. the control that's displaying the tooltip) and its Template. Then put the visual tree of the ToolTip, including bindings, into the template. Finally, reference the ToolTip in your control.
So, here's a TextBox whose Binding does validation:
<TextBox ToolTip="{StaticResource ErrorToolTip}">
<TextBox.Text>
<Binding Source="SourceProperty">
<Binding.ValidationRules>
<DataErrorValidationRule/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
It uses this ToolTip:
<ToolTip x:Key="ErrorToolTip" Style="{StaticResource ErrorToolTipStyle}"/>
And the ToolTip uses this style, which gets its content from the ValidationError property of the TextBox's binding source:
<Style x:Key="ErrorToolTipStyle" TargetType="{x:Type ToolTip}">
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="HasDropShadow" Value="True"/>
<Setter Property="DataContext" Value="{Binding Path=PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToolTip">
<Border
Name="Border"
BorderThickness="1"
BorderBrush="LightGray">
<StackPanel Orientation="Vertical">
<Label Background="Firebrick" Foreground="White" FontWeight="Bold" Margin="4">Validation error</Label>
<TextBlock Margin="10" Text="{Binding ValidationError}"/>
</StackPanel>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="HasDropShadow" Value="true">
<Setter TargetName="Border" Property="CornerRadius" Value="4"/>
<Setter TargetName="Border" Property="SnapsToDevicePixels" Value="true"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I'm not certain of this, but I think that the only part of the above that actually has to be set in the style is the DataTrigger setting the DataContext; I think most everything else could just be explicitly set in the ToolTip's visual tree. But I'm probably not thinking of something important.
<Label Content={Binding Path=Id} ToolTip={Binding Path=Name}/>
just try this
Here's a kaxaml-ready example that includes a tooltip that is a little more elaborate than just text:
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Page.Resources>
<XmlDataProvider x:Key="CharacterData">
<x:XData>
<Data xmlns="">
<Character First="Bart" Last="Simpson" Background="LightGreen" />
<Character First="Homer" Last="Simpson" Background="LightBlue" />
<Character First="Lisa" Last="Simpson" Background="Pink" />
<Character First="Maggie" Last="Simpson" Background="Yellow" />
<Character First="Marge" Last="Simpson" Background="PapayaWhip" />
</Data>
</x:XData>
</XmlDataProvider>
<ToolTip x:Key="ElaborateToolTip">
<Grid Margin="5">
<Rectangle RadiusX="6" RadiusY="6" Fill="{Binding XPath=#Background}" />
<StackPanel Orientation="Horizontal" Margin="10">
<TextBlock Text="{Binding XPath=#First}" Margin="0,0,6,0" />
<TextBlock Text="{Binding XPath=#Last}" />
</StackPanel>
</Grid>
</ToolTip>
</Page.Resources>
<ListBox ItemsSource="{Binding Source={StaticResource CharacterData}, XPath=Data/Character}">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="ToolTip" Value="{StaticResource ElaborateToolTip}" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding XPath=#First}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Page>

WPF UserControl exposing inner listitems

I'm trying to write a UserControl to display a list of items where each of these items is a title and a group of checkboxes. This whole will represent a form of data where the person filling it in is answering a list of questions with a 1 to 4 value. This all works and binds nicely to the window's ViewModel.
But I've currently got the answers hardcoded in the UserControl as follows:
<ListBox
ItemsPanel="{StaticResource HorizontalScores}"
Style="{StaticResource styleOuterListBox}"
ItemContainerStyle="{StaticResource styleOuterListBoxItem}">
<ListBoxItem>Never</ListBoxItem>
<ListBoxItem>Sometimes</ListBoxItem>
<ListBoxItem>Often</ListBoxItem>
<ListBoxItem>Always</ListBoxItem>
</ListBox>
I would like to set these from the window's XAML or from the ViewModel as they will be different for other forms but can't see the correct incantation. How do I remove the ListBoxItems from the UserControl and use databinding instead?
BigEdit ...
Ok, this is the actual user control (it looks hideous but that's not the point):
<UserControl x:Class="BussPerry.Scorer" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:vm="clr-namespace:BussPerry.ViewModel" xmlns:local="clr-namespace:BussPerry">
<UserControl.Resources>
<SolidColorBrush x:Key="SelectedBackgroundBrush" Color="Gray" />
<SolidColorBrush x:Key="SelectedForegroundBrush" Color="Red" />
<ItemsPanelTemplate x:Key="HorizontalScores">
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
<Style x:Key="styleListBox" TargetType="{x:Type ListBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBox}">
<ItemsPresenter Margin="2" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="styleListBoxItem" TargetType="{x:Type ListBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<CheckBox Name="CheckBox" Padding="1" Width="60"
IsChecked="{Binding Path=IsSelected, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}">
<ContentPresenter HorizontalAlignment="Center"/>
</CheckBox>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="CheckBox" Property="Background" Value="{StaticResource SelectedBackgroundBrush}" />
<Setter TargetName="CheckBox" Property="Foreground" Value="{StaticResource SelectedForegroundBrush}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<ListBox ItemsPanel="{StaticResource HorizontalScores}" Style="{StaticResource styleListBox}"
ItemContainerStyle="{StaticResource styleListBoxItem}" SelectedIndex="{Binding Path=Score}">
<ListBoxItem>Never</ListBoxItem>
<ListBoxItem>Sometimes</ListBoxItem>
<ListBoxItem>Often</ListBoxItem>
<ListBoxItem>Always</ListBoxItem>
</ListBox>
</UserControl>
And it's being called as follows:
<ListView
Name="listviewScores"
ItemsSource="{Binding Path=Scores}"
Margin="5"
BorderThickness="0"
Background="Transparent"
Focusable="False"
Grid.Row="3">
<ListView.View>
<GridView
ColumnHeaderContainerStyle="{StaticResource styleHiddenHeader}">
<GridView.Columns>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock
Text="{Binding Path=Index}"
HorizontalAlignment="Right" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn
DisplayMemberBinding="{Binding Path=Title}" />
<GridViewColumn >
<GridViewColumn.CellTemplate>
<DataTemplate>
<local:Scorer >
</local:Scorer>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
What I want to do is to move the Never/Sometimes/Often/Always listboxitems from being hard coded in the user control to be databound.
(Suggestions of "you don't want to do it like that" are also welcome!)
(one year later...)
I think your question is similar to mine. I have come up with a technique to expose the ItemsSource of an inner control on a UserControl. The link to my question is here:
Exposing inner Control properties for binding in WPF
I know that my solution works. What I don't know is if it violates some sacred 'best practices' out there in WPF. It 'feels' right though.
Do you want to bind a collection to a listbox?
It's pretty simple...
<ListBox ItemsSource="{Binding Answers}" />
where Answers is your collection exposed in your ViewModel.
If you're having trouble creating a custom control that exposes an ItemsSource, then you just need to inherit from ItemsControl instead of just UserControl.
EDIT:
Some assumptions:
the DataContext of the ListBox, custom control, or higher parent element is set to your
ViewModel.
the ViewModel has a
property called "Answers".
the Answers property implements
IEnumerable<>.

Resources