Dynamic template for a ComboBox in ListBox - wpf

I have a ListBox with an embedded ComboBox:
<ListBox ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<ComboBox Width="100" IsEditable="False" Height="20">
<TextBlock Text="Opt#1"></TextBlock>
<TextBlock Text="Opt#2"></TextBlock>
<TextBlock Text="Opt#3"></TextBlock>
</ComboBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I'd like to present the ComboBox as a simple text (e.g. TextBlock) when a ListBox row is not selected, and show it as a ComboBox when the ListBox row is selected.
I was thinking that replacing ComboBox template dynamically would do the trick. How to accomplish that?
Thanks,
Leszek

The best way to swap templates is to use the ItemTemplateSelector propery of the ListBox and set it to a class you create which inherits from DataTemplateSelector.
Here is a link that provides an example: http://msdn.microsoft.com/en-us/library/system.windows.controls.datatemplateselector.aspx

I would simply use a style that replace the ListBox.ItemTemplate whenever the ListBoxItem becomes selected.
Here's a quick example
<ListBox.Resources>
<DataTemplate x:Key="TextBoxTemplate">
<TextBlock Text="{Binding }" />
</DataTemplate>
<DataTemplate x:Key="ComboBoxTemplate">
<ComboBox SelectedItem="{Binding }">
<ComboBoxItem>Opt#1</ComboBoxItem>
<ComboBoxItem>Opt#2</ComboBoxItem>
<ComboBoxItem>Opt#3</ComboBoxItem>
</ComboBox>
</DataTemplate>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Template" Value="{StaticResource TextBoxTemplate}" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Template" Value="{StaticResource ComboBoxTemplate}" />
</Trigger>
</Style.Triggers>
</Style>
</ListBox.Resources>
I'd actually suggest using IsKeyboardFocusWithin instead of IsSelected as the trigger property, because templates can let you interact with them without setting the item as selected.

Thanks Josh and Rachel for pointing me in a right direction.
I came up with a solution similar to the one suggested by Rachel. My problem was I could not make ItemTemplateSelector work and I did not know how to pass the state IsSelected from my listbox. I also could not use DataTemplate because my ListBox item is much more complex than a single element (I simplified it in my previous post for the sake of example).
Anyway, I came up with the following solution. It's not very elegant but it works:
I defined a new style in Application resources:
<Style x:Key="TextBlockTemplate" TargetType="ComboBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<TextBlock Text="{Binding}" Margin="3" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I attached SelectionChanged and PreviewMouseDown handlers to my ListBox:
I defined MyListBox_PreviewMouseDown:
private void MyListBox_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
// Grab the selected list box item.
object element = (e.OriginalSource as FrameworkElement).DataContext;
var item = MyListBox.ItemContainerGenerator.ContainerFromItem(element)
as ListBoxItem;
// Mark the row in the ListBox as selected.
if (item != null)
item.IsSelected = true;
}
I defined MyListBox_SelectionChanged:
private ComboBox prevComboBox = null;
private void MyListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// Grab the list box.
ListBox list = sender as ListBox;
// Although there could be only one item selected,
// we iterate over all selected items.
foreach (MyDataItem dat in list.SelectedItems)
{
var item = list.ItemContainerGenerator.ContainerFromItem(dat) as ListBoxItem;
// FindElement is a helper method to find an element in a visual tree.
ComboBox cbo = FindElement(item, "MyComboBox") as ComboBox;
if (cbo != prevComboBox)
{
cbo.Style = null;
if (prevComboBox != null)
prevComboBox.Style =
(Style)Application.Current.Resources["TextBlockTemplate"];
prevComboBox = cbo;
}
}
}
Thanks,
Leszek

Related

Re-usable DataGridTemplateColumn Style

I would like to have a datagrid with a checkbox column with the following behavior: The checkbox is centered in the cell and the cell background is green when the checkbox is checked.
Presently I achieve this with a style for a DataGridTemplateColumn (see below). This style works fine, however the propertyname to which the checkbox is bound is referenced twice within the style.
So I cannot apply the style to another column/grid which is bound to another object/property.
Is there a way to create a re-usable solution (either with DataGridCheckBoxColumn or otherwise) where the bound property can be passed into?
My present xaml style:
<Style x:Key="IsClipToCalendarCheckBoxCellStyle" TargetType="DataGridCell">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<CheckBox IsChecked="{Binding IsClipToCalendar, UpdateSourceTrigger=PropertyChanged}"
VerticalAlignment="Center"
HorizontalAlignment="Center" />
</DataTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding IsClipToCalendar}" Value="True">
<Setter Property="Background" Value="DeepSkyBlue" />
</DataTrigger>
</Style.Triggers>
</Style>
You can create DataTemplates dynamically in code-behind of your Window:
private string StringTemplate =
#"<DataTemplate>
<StackPanel>
<Label Content=""fooTemplate"" />
<TextBox Text=""{Binding Path=fooColumn}""/>
</StackPanel>
</DataTemplate>";
and in AutoGeneratingColumn="DataGrid_AutoGeneratingColumn" you can use your DataTemplate:
private void DataGrid_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
DataTemplate dt = null;
string dtString = StringTemplate.Replace("fooColumn", e.PropertyName);
dt = GetDataTemplateForDataGrid(dtString);
DataGridTemplateColumn c = new DataGridTemplateColumn()
{
CellTemplate = dt,
Header = e.Column.Header,
HeaderTemplate = e.Column.HeaderTemplate,
HeaderStringFormat = e.Column.HeaderStringFormat,
SortMemberPath = e.PropertyName
};
e.Column = c;
}
and method to get DateTemplates:
private DataTemplate GetDataTemplateForDataGrid(string templateString)
{
ParserContext context = new ParserContext();
context.XmlnsDictionary.Add("", "http://schemas.microsoft.com/winfx/2006/xaml/presentation");
context.XmlnsDictionary.Add("x", "http://schemas.microsoft.com/winfx/2006/xaml");
return (DataTemplate)Parse(templateString, context);
}
and method to parse non-English letters, if you use binding with non-English letters:
public static object Parse(string xamlText, ParserContext parserContext)
{
return System.Windows.Markup.XamlReader.Load((Stream)new MemoryStream(Encoding.UTF8.GetBytes(xamlText)), parserContext);
}
You can try making changes in the Checked Visual state of the checkbox instead of having a trigger. I have answered it for windows phone 8.1 here. Adapt it for wpf accordingly.
Hope it helps! :)

Specify binding for HeaderTemplate programmatically

I'm having various problems with the virtualisation of views in the TabControl. Luckily, I've found something that I think will solve all my problems in this CodeProject article.
One problem is introduced by this solution though, and that is that it destroys my HeaderTemplate. The tab header has the same content as the content control.
My view uses a TabControl like this:
<TabControl
behaviors:TabItemGeneratorBehavior.ItemsSource="{Binding MyItems, Mode=OneWay}"
behaviors:TabItemGeneratorBehavior.SelectedItem="{Binding MySelectedItem, Mode=TwoWay}">
<TabControl.Resources>
<Style TargetType="TabItem">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<ContentPresenter>
<ContentPresenter.Content>
<TextBlock Text="{Binding Title}"/>
</ContentPresenter.Content>
</ContentPresenter>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.Resources>
</TabControl>
The behaviors:TabItemGeneratorBehavior is from the CodeProject article. Inside the TabItemGeneratorBehavior is a method that generates TabItems:
private void AddTabItem(object item)
{
var contentControl = new ContentControl();
var tab = new TabItem
{
DataContext = item,
Content = contentControl,
HeaderTemplate = _tabControl.ItemTemplate
};
contentControl.SetBinding(ContentControl.ContentProperty, new Binding());
tab.SetBinding(HeaderedContentControl.HeaderProperty, new Binding());
_tabControl.Items.Add(tab);
}
I think my problem is with the line that sets the binding for the HeaderProperty. How can I set the binding so that it uses the HeaderTemplate defined in my XAML above?
OP here.
The solution is to remove the HeaderTemplate assignment when creating the TabItem:
var tab = new TabItem
{
DataContext = item,
Content = contentControl,
// HeaderTemplate = _tabControl.ItemTemplate
};

Setting SelectedItem in ListView when user clicks in ItemTemplate Textbox

I have the following ListView (simplified):
<ListView Name="lvwNotes" KeyUp="lvwNotes_KeyUp">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<DockPanel Background="LightGray">
<TextBlock DockPanel.Dock="Right" Text="{Binding Path=Author}" />
<TextBlock Text="{Binding Path=Timestamp}" />
</DockPanel>
<TextBox Text="{Binding Path=Text}"
GotFocus = "lvwNotes_TextBox_GotFocus"
TextWrapping="Wrap" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
Changing the selected item through a click only works when the user clicks on the DockPanel with the TextBlocks, but not on clicking the TextBox. What I want to achieve is to set the selected item to that one containing the TextBox into which the user clicked.
I managed to get through to the ListViewItem related to the TextBox:
private void lvwNotes_TextBox_GotFocus(object sender, RoutedEventArgs e) {
DependencyObject o = Tools.GetAncestorByType((DependencyObject)sender, typeof(ListViewItem));
if (!o.Equals(null)) {
// code to select this ListViewItem
}
}
But setting
lvwNotes.SelectedIten = o ;
remains without effect. I've tried also some tricks with Dispatcher.BeginInvoke, but to be honest, I don't exactly know what I'm doing there.
Add this to your code
<ListView.Resources>
<Style TargetType="ListViewItem">
<Style.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="IsSelected" Value="True" />
</Trigger>
</Style.Triggers>
</Style>
</ListView.Resources>
The DataContext unless explicitly changed in the DataTemplate is the current item, hence:
private void lvwNotes_TextBox_GotFocus(object sender, RoutedEventArgs e)
{
var tb = (TextBox)sender;
lvwNotes.SelectedItem = tb.DataContext;
}

How to get a group of toggle buttons to act like radio buttons in WPF?

I have a group of buttons that should act like toggle buttons, but also as radio buttons where only one button can be selected / pressed down at a current time. It also need to have a state where none of the buttons are selected / pressed down.
The behavior will be kind of like Photoshop toolbar, where zero or one of the tools are selected at any time!
Any idea how this can be implemented in WPF?
This is easiest way in my opinion.
<RadioButton Style="{StaticResource {x:Type ToggleButton}}" />
Enjoy!
-- Pricksaw
The easiest way is to style a ListBox to use ToggleButtons for its ItemTemplate
<Style TargetType="{x:Type ListBox}">
<Setter Property="ListBox.ItemTemplate">
<Setter.Value>
<DataTemplate>
<ToggleButton Content="{Binding}"
IsChecked="{Binding IsSelected, Mode=TwoWay, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}}"
/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
Then you can use the SelectionMode property of the ListBox to handle SingleSelect vs MultiSelect.
<RadioButton Content="Point" >
<RadioButton.Template>
<ControlTemplate>
<ToggleButton IsChecked="{Binding IsChecked, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
Content="{Binding Content, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"/>
</ControlTemplate>
</RadioButton.Template>
</RadioButton>
it works for me, enjoy!
you could always use a generic event on the Click of the ToggleButton that sets all ToggleButton.IsChecked in a groupcontrol(Grid, WrapPanel, ...) to false with the help of the VisualTreeHelper; then re-check the sender.
Or something in the likes of that.
private void ToggleButton_Click(object sender, RoutedEventArgs e)
{
int childAmount = VisualTreeHelper.GetChildrenCount((sender as ToggleButton).Parent);
ToggleButton tb;
for (int i = 0; i < childAmount; i++)
{
tb = null;
tb = VisualTreeHelper.GetChild((sender as ToggleButton).Parent, i) as ToggleButton;
if (tb != null)
tb.IsChecked = false;
}
(sender as ToggleButton).IsChecked = true;
}
you can put grid with radiobuttons in it, and create button like template for raduiobuttons. than just programmaticaly remove check if you don't want buttons to be toggled
You can also try System.Windows.Controls.Primitives.ToggleButton
<ToggleButton Name="btnTest" VerticalAlignment="Top">Test</ToggleButton>
Then write code against the IsChecked property to mimick the radiobutton effect
private void btnTest_Checked(object sender, RoutedEventArgs e)
{
btn2.IsChecked = false;
btn3.IsChecked = false;
}
I did this for RibbonToggleButtons, but maybe it's the same for regular ToggleButtons.
I bound the IsChecked for each button to a "mode" enum value using EnumToBooleanConverter from here How to bind RadioButtons to an enum? (Specify the enum value for this button using the ConverterParameter. You should have one enum value for each button)
Then to prevent unchecking a button that's already checked, put this in your code behind for the Click event for each of the RibbonToggleButtons:
private void PreventUncheckRibbonToggleButtonOnClick ( object sender, RoutedEventArgs e ) {
// Prevent unchecking a checked toggle button - so that one always remains checked
// Cancel the click if you hit an already-checked button
var button = (RibbonToggleButton)sender;
if( button.IsChecked != null ) { // Not sure why checked can be null but that's fine, ignore it
bool notChecked = ( ! (bool)button.IsChecked );
if( notChecked ){ // I guess this means the click would uncheck it
button.IsChecked = true;
}
}
}
To help people like Julian and me (two minutes ago...). You can derive from the RadioButton like this.
class RadioToggleButton : RadioButton
{
protected override void OnToggle()
{
if (IsChecked == true) IsChecked = IsThreeState ? (bool?)null : (bool?)false;
else IsChecked = IsChecked.HasValue;
}
}
Then, you can use it like Uday Kiran suggested...
<Window x:Class="Sample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Sample"
Title="MainWindow" Height="600" Width="600">
<StackPanel>
<local:RadioToggleButton Content="Button" Style="{StaticResource {x:Type ToggleButton}}" />
</StackPanel>
</Window>
This method allows only one ToggleButton to be Checked at a time, and it also allows UnChecking.
I took a few piece of the answers and added some extra code. Now you can have different groups of toggle buttons which act like one toggle button:
<UserControl.Resources>
<Style x:Key="GroupToggleStyle" TargetType="ToggleButton">
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding GroupName, RelativeSource={RelativeSource Self}}" Value="Group1"/>
<Condition Binding="{Binding BooleanProperty}" Value="true"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="IsChecked" Value="true"/>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
And the different groups of radio buttons which look like toggle buttons:
<Radio Button GroupName="Group1" Style="{StaticResource {x:Type ToggleButton}}">
<Radio Button GroupName="Group1" Style="{StaticResource {x:Type ToggleButton}}">
<Radio Button GroupName="Group2" Style="{StaticResource {x:Type ToggleButton}}">
<Radio Button GroupName="Group3" Style="{StaticResource {x:Type ToggleButton}}">
One simplistic implementation could be where you maintain a flag in your code behind such as:
ToggleButton _CurrentlyCheckedButton;
Then assign a single Checked event handler to all your context ToggleButtons:
private void ToggleButton_Checked(object sender, RoutedEventArgs e)
{
if (_CurrentlyCheckedButton != null)
_CurrentlyCheckedButton.IsChecked = false;
_CurrentlyCheckedButton = (sender as ToggleButton);
}
And, a single Unchecked event handler:
private void ToggleButton_Unchecked(object sender, RoutedEventArgs e)
{
if (_CurrentlyCheckedButton == (sender as ToggleButton))
_CurrentlyCheckedButton = null;
}
This way you can have the 'zero or one' selection you desire.

Selecting a ListBoxItem when its inner ComboBox is focused

I have a DataTemplate that will be a templated ListBoxItem, this DataTemplate has a
ComboBox in it which when it has focus I want the ListBoxItem that this template
represents to become selected, this looks right to me. but sadly enough it doesn't work =(
So the real question here is within a DataTemplate is it possible to get or set the value
of the ListBoxItem.IsSelected property via a DataTemplate.Trigger?
<DataTemplate x:Key="myDataTemplate"
DataType="{x:Type local:myTemplateItem}">
<Grid x:Name="_LayoutRoot">
<ComboBox x:Name="testComboBox" />
</Grid>
<DataTemplate.Triggers>
<Trigger Property="IsFocused" value="true" SourceName="testComboBox">
<Setter Property="ListBoxItem.IsSelected" Value="true" />
</Trigger>
</DataTemplate.Triggers>
</DataTemplate>
<ListBox ItemTemplate="{StaticResource myDataTemplate}" />
I found a solution for your problem.
The problem is that when you have a control on your listboxitem, and the control is clicked (like for inputting text or changing the value of a combobox), the ListBoxItem does not get selected.
this should do the job:
public class FocusableListBox : ListBox
{
protected override bool IsItemItsOwnContainerOverride(object item)
{
return (item is FocusableListBoxItem);
}
protected override System.Windows.DependencyObject GetContainerForItemOverride()
{
return new FocusableListBoxItem();
}
}
--> Use this FocusableListBox in stead of the default ListBox of WPF.
And use this ListBoxItem:
public class FocusableListBoxItem : ListBoxItem
{
public FocusableListBoxItem()
{
GotFocus += new RoutedEventHandler(FocusableListBoxItem_GotFocus);
}
void FocusableListBoxItem_GotFocus(object sender, RoutedEventArgs e)
{
object obj = ParentListBox.ItemContainerGenerator.ItemFromContainer(this);
ParentListBox.SelectedItem = obj;
}
private ListBox ParentListBox
{
get
{
return (ItemsControl.ItemsControlFromItemContainer(this) as ListBox);
}
}
}
A Treeview does also have this problem, but this solution does not work for a Treeview, 'cause SelectedItem of Treeview is readonly.
So if you can help me out with the Treeview please ;-)
I found that I preferred to use this:
<Style TargetType="ListBoxItem">
<Style.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="IsSelected" Value="True"></Setter>
</Trigger>
</Style.Triggers>
</Style>
Simple and works for all the listboxitems, regardless of what's inside.
No idea why your trigger don't work. To catch the get focus event of the combo box (or any control inside a listbox item) you can use attached routed events. You could put the code also in a derived listbox if you need this behavior in other parts of your application.
XAML:
<Window x:Class="RoutedEventDemo.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Specialized="clr-namespace:System.Collections.Specialized;assembly=System"
xmlns:System="clr-namespace:System;assembly=mscorlib"
Height="300" Width="300">
<Window.Resources>
<DataTemplate x:Key="myDataTemplate">
<Grid>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}" Margin="5,0"/>
<ComboBox Width="50">
<ComboBoxItem>AAA</ComboBoxItem>
<ComboBoxItem>BBB</ComboBoxItem>
</ComboBox>
</StackPanel>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox ItemTemplate="{StaticResource myDataTemplate}">
<ListBox.ItemsSource>
<Specialized:StringCollection>
<System:String>Item 1</System:String>
<System:String>Item 2</System:String>
<System:String>Item 3</System:String>
</Specialized:StringCollection>
</ListBox.ItemsSource>
</ListBox>
</Grid>
</Window>
Code behind hooking up to all got focus events.
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace RoutedEventDemo
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
EventManager.RegisterClassHandler(typeof(UIElement),
GotFocusEvent,
new RoutedEventHandler(OnGotFocus));
}
private static void OnGotFocus(object sender, RoutedEventArgs e)
{
// Check if element that got focus is contained by a listboxitem and
// in that case selected the listboxitem.
DependencyObject parent = e.OriginalSource as DependencyObject;
while (parent != null)
{
ListBoxItem clickedOnItem = parent as ListBoxItem;
if (clickedOnItem != null)
{
clickedOnItem.IsSelected = true;
return;
}
parent = VisualTreeHelper.GetParent(parent);
}
}
}
}

Resources