hello everybody i have a listbox within which is a datatemplate.Inside it is checkbox,textbox,label...Wat i want is to get the value of the label wen the checkbox is unchecked? or any alternative as to how to access the label value but only wen the checkbox is unselected............PLease help me out.
the code is as
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Name="sp" Orientation="Horizontal" Margin="3,3,3,3" >
<CheckBox Name="chkSubject" IsChecked="{Binding RelativeSource{RelativeSource AncestorType={x:Type ListBoxItem}}, Path=IsSelected}" VerticalAlignment="Center" Margin="0,0,4,0" Unchecked="chkSubject_Unchecked">
<TextBlock FontSize="11" Text="{Binding subject_name}" />
</CheckBox>
<Label Name="lbl_idOfSub" Content="{Binding subject_id}" Visibility="Visible">
</Label>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
Since you're using binding on label, I'd go for accessing subject_id from the object the datatemplate is describing. Like this:
var subjectId = dataBoundItem.subject_id;
That's the correct way to go with MVVM and bindings.
UPDATE:
Here's the basic MVVM approach to solving this problem. First of all, I've cleaned up a bit your listbox declaration and added a trigger that sets IsSelected binding:
<ListBox ItemsSource="{Binding}">
<ListBox.Resources>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
</Style>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Name="sp" Orientation="Horizontal" Margin="3,3,3,3" >
<CheckBox Name="chkSubject" IsChecked="{Binding IsSelected}" VerticalAlignment="Center" Margin="0,0,4,0" Unchecked="chkSubject_Unchecked_1">
<TextBlock FontSize="11" Text="{Binding SubjectName}" />
</CheckBox>
<Label Name="lbl_idOfSub" Content="{Binding SubjectId}" Visibility="Visible"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Here, whenever value IsSelected on individual ListBoxItem changes, the "IsSelected" binding of the viewModel is changed. Here's the model:
public class SelectableItem : INotifyPropertyChanged
{
private string _subjectId;
private bool _isSelected;
private string _subjectName;
public string SubjectId
{
get { return _subjectId; }
set { _subjectId = value; OnPropertyChanged("SubjectId"); }
}
public bool IsSelected
{
get { return _isSelected; }
set { _isSelected = value; OnPropertyChanged("IsSelected"); }
}
public string SubjectName
{
get { return _subjectName; }
set { _subjectName = value; OnPropertyChanged("SubjectName"); }
}
// .. INotifyPropertyChangedImplementation
Your IsSelected will be set to true whenever relevant item is selected and to false whenever it is unselected. You may put your code in to the "set" item of the "IsSelected" property and check (value == false) and execute necessary piece of code as you see fit. This would be MVVM approach to the matter.
Using the event, you can do as follows:
private void chkSubject_Unchecked_1(object sender, RoutedEventArgs e)
{
FrameworkElement control = sender as FrameworkElement;
if (control == null)
return;
SelectableItem item = control.DataContext as SelectableItem;
if (item == null)
return;
string yourValue = item.SubjectId;
}
I strongly recommend you read about MVVM and bindings.
What about using the Checked and UnChecked events of your CheckBox, so that you can retrieve the value of subject_id which is binded to your Label.
Related
I am working on 'Select All' checkbox in datagrid header in wpf using mvvm pattern. On clicking the checkbox, all the checkboxes gets checked and on uncheking it, the reverse happens.
But I am unable to fetch and bind the selected items to the View Model.
My code is like this
<DataGrid Grid.Row="0" Background="LightGray" CanUserAddRows="False" AutoGenerateColumns="False" HorizontalAlignment="Left" Name="dataGridCustomers" ItemsSource="{Binding Path=UsecaseListItems}" CanUserResizeRows="False">
<DataGrid.Columns>
<DataGridTemplateColumn >
<DataGridTemplateColumn.Header>
<CheckBox x:Name="headerCheckBox" IsChecked="{Binding Path=MainWindowViewModel.AllSelected, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" Command="{Binding DoStuffCommand}" CommandParameter="{Binding ElementName=UserCaseListControl}"/>
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox Name="chkSelectAll" HorizontalAlignment="Center" IsChecked="{Binding IsChecked, ElementName=headerCheckBox, Mode=OneWay}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Binding="{Binding Path=UsecaseName}" Header="UsecaseName" IsReadOnly="True" >
<DataGridColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="FontWeight" Value="Bold"/>
</Style>
</DataGridColumn.HeaderStyle>
</DataGridTextColumn>
ViewModel is like:
private bool _IsSelected;
public bool IsSelected
{
get { return _IsSelected; }
set
{
_IsSelected = value;
OnPropertyChanged("IsSelected");
}
}
private bool _AllSelected;
public bool AllSelected
{
get { return _AllSelected; }
set
{
_AllSelected = value;
foreach (var reportListItemModel in UsecaseListItems)
{
reportListItemModel.IsSelected = this._AllSelected;
OnPropertyChanged("IsSelected");
}
OnPropertyChanged("AllSelected");
}
}
private ObservableCollection<UseCase> _usecaseListItems = new ObservableCollection<UseCase>();
public ObservableCollection<UseCase> UsecaseListItems
{
get { return _usecaseListItems; }
set {
_usecaseListItems = value;
OnPropertyChanged("UsecaseListItems");
}
}
With your code, there will be no direct binding of selected items. As all your checkboxes were bind to headerCheckBox.IsChecked.
You could bind to your UsecaseListItem.IsSelected instead, and find another way to to execute selectAll:
e.g. Define the UseCase that inherit ObservableObject and use your above code in AllSelected to change each item's IsSelected property.
Or use a Command (for example) in your headerCheckBox.
I am trying to create a DataGrid in WPF 4.0 using MVVM...
Features required -
Muti - Select rows using a checkbox (single click)
A select all checkbox to check all the checkboxes in the datagrid
Something like this -
It has been 2 days and i am not able to figure out how to solve the problem effectively..
A working example is what I need now ASAP..
I'll highly appreciate if someone has a working solution to share with me...
N please don't tell me to google this thing because none of the things worked out for me...
UPDATE -
I am using AutoGeneration of Columns
I don't want to add "IsSelected" or any of such property in my MODEL..
I am just facing 2 problems -
Firstly, "Select all" Feature i.e. checking all checkboxes on the checkbox click of the one present in the column header...(I am able to select and unselect the datagrid but not able to tick/untick the checkboxes)
Secondly, Multiple selection on mouse click without holding Ctrl Key..
When you're working with MVVM, you have to be aware of what is considered data and what is strictly UI.
Is your SelectedItems going to be part of your data, or only your UI?
If it's part of your data, you really should have an IsSelected property on your data model, even if that means extending the data class to include an IsSelected property, or creating a wrapper class that only contains bool IsSelected and object MyDataItem. The first option is probably preferred, since you could keep AutoGenerateColumns="True", and it makes the column bindings simpler.
Then you would just bind your DataGridRow.SelectedItem to the IsSelected property of the data item:
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
But if your SelectedItems is only for the UI, or if you are breaking the MVVM pattern for some reason in this instance, than you can create the unbound CheckBox and use some code behind to ensure the CheckBox is correctly synchronized to the SelectedItem.
I did a quick sample app, and here is what my code looked like:
First off, I just added the unbound CheckBox column to the column list using a DataGridTemplateColumn. This will get added before the AutoGenerateColumns list of columns.
<DataGrid x:Name="TestDataGrid" ItemsSource="{Binding Test}"
SelectionMode="Extended" CanUserAddRows="False"
PreviewMouseLeftButtonDown="TestDataGrid_PreviewMouseLeftButtonDown_1">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox x:Name="TestCheckBox"
PreviewMouseLeftButtonDown="CheckBox_PreviewMouseLeftButtonDown" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Second, I added a PreviewMouseDown event to the CheckBox to make it set the IsSelected property of the row.
private void CheckBox_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var chk = (CheckBox)sender;
var row = VisualTreeHelpers.FindAncestor<DataGridRow>(chk);
var newValue = !chk.IsChecked.GetValueOrDefault();
row.IsSelected = newValue;
chk.IsChecked = newValue;
// Mark event as handled so that the default
// DataGridPreviewMouseDown doesn't handle the event
e.Handled = true;
}
It needs to navigate the VisualTree to find the DataGridRow associated with the clicked CheckBox to select it, and to make life easier I am using some custom VisualTreeHelpers that I have on my blog to find the DataGridRow. You can use the same code, or you can create your own method for searching the VisualTree.
And last of all, if the user clicks on anywhere other than the CheckBox, we want to disable the default DataGrid selection event. This ensures that the IsSelected value will only change when you click on the CheckBox.
There are multiple ways of doing this that will disable the selection at different levels, but to make life simple I just disabled the DataGrid.PreviewMouseLeftButtonDown event if the user didn't click on the CheckBox.
private void TestDataGrid_PreviewMouseLeftButtonDown_1(object sender, MouseButtonEventArgs e)
{
var chk = VisualTreeHelpers.FindAncestor<CheckBox>((DependencyObject)e.OriginalSource, "TestCheckBox");
if (chk == null)
e.Handled = true;
}
I using my custom VisualTreeHelpers again to navigate the visual tree and find out if the CheckBox was clicked on, and cancelling the event if the user clicked on anywhere other than the CheckBox.
As for your 2nd request of adding a CheckBox to SelectAll or UnselectAll items, this would once again be dependent on if your selection is part of the UI or the data.
If it's part of the UI, simply add a CheckBox to the DataGridTemplateColumn.HeaderTemplate, and when it's clicked, loop through the DataGrid.Rows, find the CheckBox in the first column, and check or uncheck it.
If it's part of the data you could still do the same thing (only set the bound value in the DataGrid.Items instead of the CheckBox.IsChecked from the DataGrid.Rows), or you could do as Adolfo Perez suggested, and bind it to a property on the ViewModel.
For an MVVM Solution you could try this:
<StackPanel>
<DataGrid ItemsSource="{Binding Path=TestItems}" AutoGenerateColumns="False" Name="MyDataGrid"
CanUserAddRows="False">
<DataGrid.Columns>
<DataGridCheckBoxColumn Binding="{Binding IsSelected}" Width="50" >
<DataGridCheckBoxColumn.HeaderTemplate>
<DataTemplate x:Name="dtAllChkBx">
<CheckBox Name="cbxAll" Content="All" IsChecked="{Binding Path=DataContext.AllSelected,RelativeSource={RelativeSource AncestorType=DataGrid}}"/>
</DataTemplate>
</DataGridCheckBoxColumn.HeaderTemplate>
</DataGridCheckBoxColumn>
<DataGridTemplateColumn Header="Name" Width="SizeToCells" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
In your ViewModel:
private void PopulateTestItems()
{
TestItems = new ObservableCollection<TestItem>();
for (int i = 0; i < 5; i++)
{
TestItem ti = new TestItem();
ti.Name = "TestItem" + i;
ti.IsSelected = true;
TestItems.Add(ti);
}
}
private bool _AllSelected;
public bool AllSelected
{
get { return _AllSelected; }
set
{
_AllSelected = value;
TestItems.ToList().ForEach(x => x.IsSelected = value);
NotifyPropertyChanged(m => m.AllSelected);
}
}
private ObservableCollection<TestItem> _TestItems;
public ObservableCollection<TestItem> TestItems
{
get { return _TestItems; }
set
{
_TestItems = value;
NotifyPropertyChanged(m => m.TestItems);
}
}
And finally the sample Model class:
public class TestItem : ModelBase<TestItem>
{
private string _Name;
public string Name
{
get { return _Name; }
set
{
_Name = value;
NotifyPropertyChanged(m => m.Name);
}
}
private bool _IsSelected;
public bool IsSelected
{
get { return _IsSelected; }
set
{
_IsSelected = value;
NotifyPropertyChanged(m => m.IsSelected);
}
}
}
Most of the code above should be self-explanatory but if you have any question let me know
Your view can be something like
<DataGrid Name="SomeDataGrid" Grid.Row="0" ItemsSource="{Binding Path=SomeCollection}">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.HeaderTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding
RelativeSource={RelativeSource AncestorType={x:Type DataGrid}},
Path=DataContext.AllItemsAreChecked}" />
</DataTemplate>
</DataGridTemplateColumn.HeaderTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="{x:Type local:SomeType}">
<CheckBox Focusable="False" IsChecked="{Binding Path=IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
HorizontalAlignment="Left" VerticalAlignment="Center"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="RandomNumber" Width="160">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="{x:Type local:SomeType}">
<TextBlock Text="{Binding Path=RandomNumber}" TextWrapping="Wrap" HorizontalAlignment="Left" VerticalAlignment="Center"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Date" Width="160">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="{x:Type local:SomeType}">
<TextBlock Text="{Binding Path=Date}" TextWrapping="Wrap" HorizontalAlignment="Left" VerticalAlignment="Center"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Time" Width="50">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="{x:Type local:SomeType}">
<TextBlock Text="{Binding Time}" HorizontalAlignment="Left" VerticalAlignment="Center"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
And in viewmodel
SomeCollection binding property is an observablecollection
sometype contains properties like IsSelected , RandomNumber ,Date , Time
for eg:
class ViewModel
{
public ObservableCollection<SomeType> SomeCollection{get;set;}
}
class SomeType
{
public string Date {get;set;}
public string Time {get;set;}
public string RandomNumber {get;set;}
public bool IsSelected {get;set;}
}
I have a WPF Combobox which is filled with, say, Customer objects. I have a DataTemplate:
<DataTemplate DataType="{x:Type MyAssembly:Customer}">
<StackPanel>
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Address}" />
</StackPanel>
</DataTemplate>
This way, when I open my ComboBox, I can see the different Customers with their Name and, below that, the Address.
But when I select a Customer, I only want to display the Name in the ComboBox. Something like:
<DataTemplate DataType="{x:Type MyAssembly:Customer}">
<StackPanel>
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
Can I select another Template for the selected item in a ComboBox?
Solution
With help from the answers, I solved it like this:
<UserControl.Resources>
<ControlTemplate x:Key="SimpleTemplate">
<StackPanel>
<TextBlock Text="{Binding Name}" />
</StackPanel>
</ControlTemplate>
<ControlTemplate x:Key="ExtendedTemplate">
<StackPanel>
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Address}" />
</StackPanel>
</ControlTemplate>
<DataTemplate x:Key="CustomerTemplate">
<Control x:Name="theControl" Focusable="False" Template="{StaticResource ExtendedTemplate}" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ComboBoxItem}}, Path=IsSelected}" Value="{x:Null}">
<Setter TargetName="theControl" Property="Template" Value="{StaticResource SimpleTemplate}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</UserControl.Resources>
Then, my ComboBox:
<ComboBox ItemsSource="{Binding Customers}"
SelectedItem="{Binding SelectedCustomer}"
ItemTemplate="{StaticResource CustomerTemplate}" />
The important part to get it to work was Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ComboBoxItem}}, Path=IsSelected}" Value="{x:Null}" (the part where value should be x:Null, not True).
The issue with using the DataTrigger/Binding solution mentioned above are two-fold. The first is you actually end up with a binding warning that you can't find the relative source for the selected item. The bigger issue however is that you've cluttered up your data templates and made them specific to a ComboBox.
The solution I present better follows WPF designs in that it uses a DataTemplateSelector on which you can specify separate templates using its SelectedItemTemplate and DropDownItemsTemplate properties as well as ‘selector’ variants for both.
Note: Updated for C#9 with nullability enabled and using pattern matching during the search
public class ComboBoxTemplateSelector : DataTemplateSelector {
public DataTemplate? SelectedItemTemplate { get; set; }
public DataTemplateSelector? SelectedItemTemplateSelector { get; set; }
public DataTemplate? DropdownItemsTemplate { get; set; }
public DataTemplateSelector? DropdownItemsTemplateSelector { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container) {
var itemToCheck = container;
// Search up the visual tree, stopping at either a ComboBox or
// a ComboBoxItem (or null). This will determine which template to use
while(itemToCheck is not null
and not ComboBox
and not ComboBoxItem)
itemToCheck = VisualTreeHelper.GetParent(itemToCheck);
// If you stopped at a ComboBoxItem, you're in the dropdown
var inDropDown = itemToCheck is ComboBoxItem;
return inDropDown
? DropdownItemsTemplate ?? DropdownItemsTemplateSelector?.SelectTemplate(item, container)
: SelectedItemTemplate ?? SelectedItemTemplateSelector?.SelectTemplate(item, container);
}
}
To make it easier to use in XAML, I've also included a markup extension that simply creates and returns the above class in its ProvideValue function.
public class ComboBoxTemplateSelectorExtension : MarkupExtension {
public DataTemplate? SelectedItemTemplate { get; set; }
public DataTemplateSelector? SelectedItemTemplateSelector { get; set; }
public DataTemplate? DropdownItemsTemplate { get; set; }
public DataTemplateSelector? DropdownItemsTemplateSelector { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
=> new ComboBoxTemplateSelector(){
SelectedItemTemplate = SelectedItemTemplate,
SelectedItemTemplateSelector = SelectedItemTemplateSelector,
DropdownItemsTemplate = DropdownItemsTemplate,
DropdownItemsTemplateSelector = DropdownItemsTemplateSelector
};
}
And here's how you use it. Nice, clean and clear and your templates stay 'pure'
Note: 'is:' here is my xmlns mapping for where I put the class in code. Make sure to import your own namespace and change 'is:' as appropriate.
<ComboBox x:Name="MyComboBox"
ItemsSource="{Binding Items}"
ItemTemplateSelector="{is:ComboBoxTemplateSelector
SelectedItemTemplate={StaticResource MySelectedItemTemplate},
DropdownItemsTemplate={StaticResource MyDropDownItemTemplate}}" />
You can also use DataTemplateSelectors if you prefer...
<ComboBox x:Name="MyComboBox"
ItemsSource="{Binding Items}"
ItemTemplateSelector="{is:ComboBoxTemplateSelector
SelectedItemTemplateSelector={StaticResource MySelectedItemTemplateSelector},
DropdownItemsTemplateSelector={StaticResource MyDropDownItemTemplateSelector}}" />
Or mix and match! Here I'm using a template for the selected item, but a template selector for the DropDown items.
<ComboBox x:Name="MyComboBox"
ItemsSource="{Binding Items}"
ItemTemplateSelector="{is:ComboBoxTemplateSelector
SelectedItemTemplate={StaticResource MySelectedItemTemplate},
DropdownItemsTemplateSelector={StaticResource MyDropDownItemTemplateSelector}}" />
Additionally, if you don't specify a Template or a TemplateSelector for the selected or dropdown items, it simply falls back to the regular resolving of data templates based on data types, again, as you would expect. So, for instance, in the below case, the selected item has its template explicitly set, but the dropdown will inherit whichever data template applies for the DataType of the object in the data context.
<ComboBox x:Name="MyComboBox"
ItemsSource="{Binding Items}"
ItemTemplateSelector="{is:ComboBoxTemplateSelector
SelectedItemTemplate={StaticResource MyTemplate} />
Enjoy!
Simple solution:
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Address}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=ComboBoxItem}}" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
</DataTemplate>
(Note that the element that is selected and displayed in the box and not the list is not inside a ComboBoxItem hence the trigger on Null)
If you want to switch out the whole template you can do that as well by using the trigger to e.g. apply a different ContentTemplate to a ContentControl. This also allows you to retain a default DataType-based template selection if you just change the template for this selective case, e.g.:
<ComboBox.ItemTemplate>
<DataTemplate>
<ContentControl Content="{Binding}">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=ComboBoxItem}}"
Value="{x:Null}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<!-- ... -->
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
</ComboBox.ItemTemplate>
Note that this method will cause binding errors as the relative source is not found for the selected item. For an alternate approach see MarqueIV's answer.
In addition to what said by H.B. answer, the Binding Error can be avoided with a Converter. The following example is based from the Solution edited by the OP himself.
The idea is very simple: bind to something that alway exists (Control) and do the relevant check inside the converter.
The relevant part of the modified XAML is the following. Please note that Path=IsSelected was never really needed and ComboBoxItem is replaced with Control to avoid the binding errors.
<DataTrigger Binding="{Binding
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Control}},
Converter={StaticResource ComboBoxItemIsSelectedConverter}}"
Value="{x:Null}">
<Setter TargetName="theControl" Property="Template" Value="{StaticResource SimpleTemplate}" />
</DataTrigger>
The C# Converter code is the following:
public class ComboBoxItemIsSelectedConverter : IValueConverter
{
private static object _notNull = new object();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// value is ComboBox when the item is the one in the closed combo
if (value is ComboBox) return null;
// all the other items inside the dropdown will go here
return _notNull;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
I was going to suggest using the combination of an ItemTemplate for the combo items, with the Text parameter as the title selection, but I see that ComboBox doesn't respect the Text parameter.
I dealt with something similar by overriding the ComboBox ControlTemplate. Here's the MSDN website with a sample for .NET 4.0.
In my solution, I change the ContentPresenter in the ComboBox template to have it bind to Text, with its ContentTemplate bound to a simple DataTemplate that contains a TextBlock like so:
<DataTemplate x:Uid="DataTemplate_1" x:Key="ComboSelectionBoxTemplate">
<TextBlock x:Uid="TextBlock_1" Text="{Binding}" />
</DataTemplate>
with this in the ControlTemplate:
<ContentPresenter Name="ContentSite" IsHitTestVisible="False" Content="{TemplateBinding Text}" ContentTemplate="{StaticResource ComboSelectionBoxTemplate}" Margin="3,3,23,3" VerticalAlignment="Center" HorizontalAlignment="Left"/>
With this binding link, I am able to control the Combo selection display directly via the Text parameter on the control (which I bind to an appropriate value on my ViewModel).
I used next approach
<UserControl.Resources>
<DataTemplate x:Key="SelectedItemTemplate" DataType="{x:Type statusBar:OffsetItem}">
<TextBlock Text="{Binding Path=ShortName}" />
</DataTemplate>
</UserControl.Resources>
<StackPanel Orientation="Horizontal">
<ComboBox DisplayMemberPath="FullName"
ItemsSource="{Binding Path=Offsets}"
behaviors:SelectedItemTemplateBehavior.SelectedItemDataTemplate="{StaticResource SelectedItemTemplate}"
SelectedItem="{Binding Path=Selected}" />
<TextBlock Text="User Time" />
<TextBlock Text="" />
</StackPanel>
And the behavior
public static class SelectedItemTemplateBehavior
{
public static readonly DependencyProperty SelectedItemDataTemplateProperty =
DependencyProperty.RegisterAttached("SelectedItemDataTemplate", typeof(DataTemplate), typeof(SelectedItemTemplateBehavior), new PropertyMetadata(default(DataTemplate), PropertyChangedCallback));
public static void SetSelectedItemDataTemplate(this UIElement element, DataTemplate value)
{
element.SetValue(SelectedItemDataTemplateProperty, value);
}
public static DataTemplate GetSelectedItemDataTemplate(this ComboBox element)
{
return (DataTemplate)element.GetValue(SelectedItemDataTemplateProperty);
}
private static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var uiElement = d as ComboBox;
if (e.Property == SelectedItemDataTemplateProperty && uiElement != null)
{
uiElement.Loaded -= UiElementLoaded;
UpdateSelectionTemplate(uiElement);
uiElement.Loaded += UiElementLoaded;
}
}
static void UiElementLoaded(object sender, RoutedEventArgs e)
{
UpdateSelectionTemplate((ComboBox)sender);
}
private static void UpdateSelectionTemplate(ComboBox uiElement)
{
var contentPresenter = GetChildOfType<ContentPresenter>(uiElement);
if (contentPresenter == null)
return;
var template = uiElement.GetSelectedItemDataTemplate();
contentPresenter.ContentTemplate = template;
}
public static T GetChildOfType<T>(DependencyObject depObj)
where T : DependencyObject
{
if (depObj == null) return null;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
var child = VisualTreeHelper.GetChild(depObj, i);
var result = (child as T) ?? GetChildOfType<T>(child);
if (result != null) return result;
}
return null;
}
}
worked like a charm. Don't like pretty much Loaded event here but you can fix it if you want
Yes. You use a Template Selector to determine which template to bind at run-time. Thus if IsSelected = False then Use this template, if IsSelected = True, use this other template.
Of Note:
Once you implement your template selector, you will need to give the templates keynames.
I propose this solution without DataTemplateSelector, Trigger, binding nor behavior.
The first step is to put the ItemTemplate (of the selected element) in the ComboBox resources and the ItemTemplate (of the item in the drop down menu) in the ComboBox.ItemsPanel resources and give both resources the same key.
The second step is to postpone the ItemTemplate resolution at run time by using both a ContentPresenter and a DynamicResource in the actual ComboBox.ItemTemplate implementation.
<ComboBox ItemsSource="{Binding Items, Mode=OneWay}">
<ComboBox.Resources>
<!-- Define ItemTemplate resource -->
<DataTemplate x:Key="ItemTemplate" DataType="viewModel:ItemType">
<TextBlock Text="{Binding FieldOne, Mode=OneWay}" />
</DataTemplate>
</ComboBox.Resources>
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Grid.IsSharedSizeScope="True"
IsItemsHost="True">
<StackPanel.Resources>
<!-- Redefine ItemTemplate resource -->
<DataTemplate x:Key="ItemTemplate" DataType="viewModel:ItemType">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="GroupOne" />
<ColumnDefinition Width="10" SharedSizeGroup="GroupSpace" />
<ColumnDefinition Width="Auto" SharedSizeGroup="GroupTwo" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding FieldOne, Mode=OneWay}" />
<TextBlock Grid.Column="2" Text="{Binding FieldTwo, Mode=OneWay}" />
</Grid>
</DataTemplate>
</StackPanel.Resources>
</StackPanel>
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
<ComboBox.ItemTemplate>
<DataTemplate>
<ContentPresenter ContentTemplate="{DynamicResource ItemTemplate}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
I have a TextBlock in resource data template:
<DataTemplate x:Key="MyDataTemplate" ItemsSource="{Binding MySource}">
<TextBlock x:Name="MyText" Text="{Binding ???}" />
</DataTemplate>
that I want to bind with Content of checked radio button:
<RadioButton GroupName="MyGroup" Content="Code" />
<RadioButton GroupName="MyGroup" Content="Description" />
if Code radio button is selected, then I want the text become Text={Binding Code}.
Please help, thank you.
Assuming this is backed by a viewmodel, you would set up your viewmodel thusly:
bool isCodeChecked;
public bool IsCodeChecked
{
get { return isCodeChecked; }
set
{
if(value == isCodeChecked) return;
isCodeChecked = value;
// raise notification that ***MyText*** property has changed (INotifyPropertyChanged interface)
}
public string MyText
{
get { return IsCodeChecked ? "Code" : "Description"; }
}
Then set up your XAML:
<RadioButton GroupName="MyGroup" Content="Code" IsChecked="{Binding IsCodeChecked, Mode=OneWayToSource}" />
<DataTemplate x:Key="MyDataTemplate" ItemsSource="{Binding MySource}">
<TextBlock x:Name="MyText" Text="{Binding MyText}" />
</DataTemplate>
The binding on the CheckBox will cause the boolean property on the viewmodel to be updated, which in turn will notify the textbox to update its bound value.
I created 2 DataTamplate in resources, and I switch my TreeView's ItemTemplate from code behind
if (ViewByCodeRadioButton.IsChecked == true)
MyTreeView.ItemTemplate = Resources["MyDataTemplateCode"] as DataTemplate;
else
MyTreeView.ItemTemplate = Resources["MyDataTemplateDesc"] as DataTemplate;
What I need to accomplish is a ComboBox that shows People. When you expand the drop-down it shows FirstName and LastName, but when you select a person, the value shown at the combobox should be just the person's first name.
I have the following ItemTemplate:
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding FirstName}" />
<TextBlock Text=" " />
<TextBlock Text="{Binding LastName}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
What else should I do to display only the first name when one item is selected?
Thanks!
EDIT
Changed the question slightly: What if I have the person's picture and instead of showing just the first name when a person is selected, I want to show only the picture. In other words, how can I have two separate templates - one for the drop-down and one for the selected item?
Here's the solution:
<ComboBox>
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<ContentControl x:Name="content" Content="{Binding}" ContentTemplate="{StaticResource ComplexTemplate}"/>
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ComboBoxItem}}" Value="{x:Null}">
<Setter TargetName="content" Property="ContentTemplate" Value="{StaticResource SimpleTemplate}"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Basically, you create one more layer of DataTemplate here. ComboBox'es ItemTemplate always stays the same. But the content inside that template adjusts to the condition you are interested in.
The trick to discriminate dropped-down combobox items against selected-area combobox item is that selected-area is not really enclosed in ComboBoxItem object, it's part of ComboBox control itself. So FindAncestor for ComboBoxItem returns null, which we use in the trigger above.
I got it. I just needed to add the following to my ComboBox:
IsEditable="True" IsReadOnly="True" TextSearch.TextPath="FirstName"
Put a Trigger on the DataTemplate. The trigger should check the IsSelected property (the DataTemplate will need a TargetType set for this to work). If it is selected, you can set the Visibility of your TextBlocks to Collapsed, and set the Visibility of the Image to Visible. Then do the opposite for the case that it is not selected.
Another option is to use ItemTemplateSelector instead of ItemTemplate. I've been using it the following way.
ComboBoxItemTemplateSelector derives from DataTemplateSelector and has two attached properties, SelectedTemplate and DropDownTemplate. Then we set the DataTemplates from Xaml like this
<ComboBox ItemsSource="{Binding Persons}"
ItemTemplateSelector="{StaticResource ComboBoxItemTemplateSelector}">
<ts:ComboBoxItemTemplateSelector.SelectedTemplate>
<DataTemplate>
<TextBlock Text="{Binding FirstName}" />
</DataTemplate>
</ts:ComboBoxItemTemplateSelector.SelectedTemplate>
<ts:ComboBoxItemTemplateSelector.DropDownTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding FirstName}" />
<TextBlock Text=" " />
<TextBlock Text="{Binding LastName}" />
</StackPanel>
</DataTemplate>
</ts:ComboBoxItemTemplateSelector.DropDownTemplate>
</ComboBox>
In SelectTemplate we check if the current container is wrapped in a ComboBoxItem and if it is, we return the DropDownTemplate. Otherwise we return SelectedTemplate.
public class ComboBoxItemTemplateChooser : DataTemplateSelector
{
#region SelectedTemplate..
#region DropDownTemplate..
public override DataTemplate SelectTemplate(object item,
DependencyObject container)
{
ComboBox parentComboBox = null;
ComboBoxItem comboBoxItem = container.GetVisualParent<ComboBoxItem>();
if (comboBoxItem == null)
{
parentComboBox = container.GetVisualParent<ComboBox>();
return ComboBoxItemTemplateChooser.GetSelectedTemplate(parentComboBox);
}
parentComboBox = ComboBox.ItemsControlFromItemContainer(comboBoxItem) as ComboBox;
return ComboBoxItemTemplateChooser.GetDropDownTemplate(parentComboBox);
}
}
A small demo project that uses this can be downloaded here: ComboBoxItemTemplateDemo.zip
I also made a short blog-post about this here: Different ComboBox ItemTemplate for dropdown. It also shows the other obvious way of doing the same thing but with properties instead of attached properties in ComboBoxItemTemplateSelector.
Oh, and GetVisualParent. Everyone seems to have their own implementations of this but anyway, here's the one I'm using
public static class DependencyObjectExtensions
{
public static T GetVisualParent<T>(this DependencyObject child) where T : Visual
{
while ((child != null) && !(child is T))
{
child = VisualTreeHelper.GetParent(child);
}
return child as T;
}
}
I used next approach
<UserControl.Resources>
<DataTemplate x:Key="SelectedItemTemplate" DataType="{x:Type statusBar:OffsetItem}">
<TextBlock Text="{Binding Path=ShortName}" />
</DataTemplate>
</UserControl.Resources>
<StackPanel Orientation="Horizontal">
<ComboBox DisplayMemberPath="FullName"
ItemsSource="{Binding Path=Offsets}"
behaviors:SelectedItemTemplateBehavior.SelectedItemDataTemplate="{StaticResource SelectedItemTemplate}"
SelectedItem="{Binding Path=Selected}" />
<TextBlock Text="User Time" />
<TextBlock Text="" />
</StackPanel>
And the behavior
public static class SelectedItemTemplateBehavior
{
public static readonly DependencyProperty SelectedItemDataTemplateProperty =
DependencyProperty.RegisterAttached("SelectedItemDataTemplate", typeof(DataTemplate), typeof(SelectedItemTemplateBehavior), new PropertyMetadata(default(DataTemplate), PropertyChangedCallback));
public static void SetSelectedItemDataTemplate(this UIElement element, DataTemplate value)
{
element.SetValue(SelectedItemDataTemplateProperty, value);
}
public static DataTemplate GetSelectedItemDataTemplate(this ComboBox element)
{
return (DataTemplate)element.GetValue(SelectedItemDataTemplateProperty);
}
private static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var uiElement = d as ComboBox;
if (e.Property == SelectedItemDataTemplateProperty && uiElement != null)
{
uiElement.Loaded -= UiElementLoaded;
UpdateSelectionTemplate(uiElement);
uiElement.Loaded += UiElementLoaded;
}
}
static void UiElementLoaded(object sender, RoutedEventArgs e)
{
UpdateSelectionTemplate((ComboBox)sender);
}
private static void UpdateSelectionTemplate(ComboBox uiElement)
{
var contentPresenter = GetChildOfType<ContentPresenter>(uiElement);
if (contentPresenter == null)
return;
var template = uiElement.GetSelectedItemDataTemplate();
contentPresenter.ContentTemplate = template;
}
public static T GetChildOfType<T>(DependencyObject depObj)
where T : DependencyObject
{
if (depObj == null) return null;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
var child = VisualTreeHelper.GetChild(depObj, i);
var result = (child as T) ?? GetChildOfType<T>(child);
if (result != null) return result;
}
return null;
}
}
worked like a charm. Don't like pretty much Loaded event here but you can fix it if you want