Looking for a WPF ComboBox with checkboxes - wpf

My google skills fail me. Anyone heard of a control like that for WPF. I am trying to make it look like this (winforms screenshot).

You can do this yourself by setting the DataTemplate of the combo box. This article shows you how - for a listbox, but the principle is the same.
Another article here is perhaps a better fit for what you are trying to do, simple set the first column of the item template to be a checkbox and bind it to a bool on your business object.
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding IsSelected}"
Width="20" />
<TextBlock Text="{Binding DayOfWeek}"
Width="100" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>

There is my combobox. I use Martin Harris code and code from this link Can a WPF ComboBox display alternative text when its selection is null?
<ComboBox Name="cbObjects" Grid.Column="1" Grid.Row="1" VerticalAlignment="Center" Margin="2,2,6,0" SelectionChanged="OnCbObjectsSelectionChanged" >
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding IsSelected}" Width="20" VerticalAlignment="Center" Checked="OnCbObjectCheckBoxChecked" Unchecked="OnCbObjectCheckBoxChecked" />
<TextBlock Text="{Binding ObjectData}" VerticalAlignment="Center" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock IsHitTestVisible="False" Name="tbObjects" Text="Выберите объекты..." Grid.Column="1" Grid.Row="1" VerticalAlignment="Center" Margin="6,2,6,0" />
Small class for datasource:
public class SelectableObject <T> {
public bool IsSelected { get; set; }
public T ObjectData { get; set; }
public SelectableObject(T objectData) {
ObjectData = objectData;
}
public SelectableObject(T objectData, bool isSelected) {
IsSelected = isSelected;
ObjectData = objectData;
}
}
And there is two handler - one for handling CheckBox clicked and one for forming Text for ComboBox.
private void OnCbObjectCheckBoxChecked(object sender, RoutedEventArgs e) {
StringBuilder sb = new StringBuilder();
foreach (SelectableObject<tblObject> cbObject in cbObjects.Items)
{
if (cbObject.IsSelected)
sb.AppendFormat("{0}, ", cbObject.ObjectData.Description);
}
tbObjects.Text = sb.ToString().Trim().TrimEnd(',');
}
private void OnCbObjectsSelectionChanged(object sender, SelectionChangedEventArgs e) {
ComboBox comboBox = (ComboBox)sender;
comboBox.SelectedItem = null;
}
For ComboBox.ItemsSource I use
ObservableCollection<SelectableObject<tblObject>>
where tblObject is type of my object, a list of which I want to display in ComboBox.
I hope this code is useful to someone!

Give a try to CheckComboBox from Extended WPF Toolkit.
The main advantage for me is having two lists for binding:
all items available for selection
just selected items
I find this approach more practical. In addition you can specify value and display members of the collections you're binding.
If you don't want to bring a bunch of other controls with CheckComboBox, you can get the source code of it, it's pretty straightforward (need to bring Selector class as well).

ComboBox with Checkboxes
<ComboBox Height="16" Width="15">
<CheckBox Content="First Checkbox" />
<CheckBox Content="Second Checkbox" />
<CheckBox Content="Third Checkbox" />
<TextBlock Text="Some Text" />
</ComboBox>
The provided answers surprisingly didn't work for me, I tried many variations and kept getting error messages about the checkbox not being part of combobox and the data context seemed to be broken.
In the end I didn't have to do anything involving data templates or any code behind and my bindings are working fine (not shown in example)
I must say I'm happy with how easy this turned out to be after reading all the answers.

Related

Binding to a viewmodel property in a DataTemplate

I'm fairly new to XAML but enjoying learning it. The thing I'm really struggling with is binding a property to an element in a DataTemplate.
I have created a simple WPF example to, (hopefully,) explain my problem.
I this example I am trying to bind the Visibility property of a CheckBox in a DataTemplate to a Property in my viewmodel. (Using this scenario purely for learning/demo.)
I have a simple DataModel named Item, but is of little relevance in this example.
class Item : INotifyPropertyChanged
{
// Fields...
private bool _IsRequired;
private string _ItemName;
And a fairly simple View Model named ItemViewModel.
class ItemViewModel : INotifyPropertyChanged
{
private ObservableCollection<Item> _Items;
private bool _IsCheckBoxChecked;
private bool _IsCheckBoxVisible;
public ObservableCollection<Item> Items
{
get { return _Items; }
set { _Items = value; }
}
public bool IsCheckBoxChecked
{
get { return _IsCheckBoxChecked; }
set
{
if (_IsCheckBoxChecked == value)
return;
_IsCheckBoxChecked = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("IsCheckBoxChecked"));
PropertyChanged(this, new PropertyChangedEventArgs("IsCheckBoxVisible"));
}
}
}
public bool IsCheckBoxVisible
{
get { return !_IsCheckBoxChecked; }
set
{
if (_IsCheckBoxVisible == value)
return;
_IsCheckBoxVisible = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("IsCheckBoxVisible"));
}
(Constructors and INotifyPropertyChanged implementation omitted for brevity.)
Controls laid out in MainPage.xaml as follows.
<Window.Resources>
<local:VisibilityConverter x:Key="VisibilityConverter"/>
</Window.Resources>
<Window.DataContext>
<local:ItemViewModel/>
</Window.DataContext>
<Grid>
<StackPanel>
<CheckBox x:Name="checkBox" Content="Hide CheckBoxes" FontSize="14" IsChecked="{Binding IsCheckBoxChecked, Mode=TwoWay}" />
<ListView ItemsSource="{Binding Items}" HorizontalContentAlignment="Stretch" >
<ListView.ItemTemplate >
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding ItemName}"/>
<CheckBox Grid.Column="1" Visibility="{Binding IsCheckBoxVisible, Converter={StaticResource VisibilityConverter}}" >
<CheckBox.DataContext>
<local:ItemViewModel/>
</CheckBox.DataContext>
</CheckBox>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<StackPanel Orientation="Horizontal" Margin="4,4,0,0">
<TextBlock Text="IsCheckBoxVisible:"/>
<TextBlock Text="{Binding IsCheckBoxVisible}" Margin="4,0,0,0" FontWeight="Bold" />
</StackPanel >
<Button Content="Button" Visibility="{Binding IsCheckBoxVisible, Converter={StaticResource VisibilityConverter}}" Margin="4,4,4,4"/>
</StackPanel>
</Grid>
The 'Hide CheckBoxes' checkbox is bound to IsCheckBoxChecked and is used to update IsCheckBoxVisible. I've also added a couple of extra controls below the DataTemplate to prove, (to myself,) the everything works.)
I have also implemented Jeff Wilcox's value converter. (Thank you.) http://www.jeff.wilcox.name/2008/07/visibility-type-converter/
When I run the app, checking and unchecking the 'Hide Checkbox', controls outside the DataTemplate function as expected but, alas, the Checkbox inside the data template remains unchanged.
I have had success with:
IsVisible="{Binding IsChecked, Converter={StaticResource VisibilityConverter}, ElementName=checkBox}"
But I'm not just trying mimic another control but make decisions based on a value.
I would REALLY appreciate any help or advice you can offer.
Thank you.
When you are in a DataTemplate, your DataContext is the data templated object, in this case an Item. Thus, the DataContext of the CheckBox in the DataTemplate is an Item, not your ItemViewModel. You can see this by your <TextBlock Text="{Binding ItemName}"/>, which binds to a property on the Item class. The Binding to IsCheckBoxVisible is trying to find a property called IsCheckBoxVisible on Item.
There are a couple of ways around this, but by far the easiest is to do this:
On your Window (in the xaml), give it and x:Name. Eg:
<Window [...blah blah...]
x:Name="MyWindow">
Change your binding to look like this:
<CheckBox Grid.Column="1"
Visibility="{Binding DataContext.IsCheckBoxVisible, ElementName=MyWindow, Converter={StaticResource VisibilityConverter}}">
We're using the Window as the source for the Binding, then looking at its DataContext property (which should be your ItemViewModel, and then pulling off the IsCheckBoxVisible property.
Another option, if you want something fancier, is to use a proxy object to reference your DataContext. See this article on DataContextProxy.

Failing to Add UserControl To Existing Grid on SelectedItem in ListBox

I am new into WPF. I am currently developing an application where my solution in MSVC# 2010 has 2 projects. One project(i.e.MSPBoardControl) has a mainwindow.xaml and I have added another wpf window ConnectView.xaml. By using the grid in mainwindow.xaml I am able to add the Connectview.xaml view to my application in mainwindow.xaml.cs. My 2nd project is a classlibrary which has a usercontrol(i.e. Voltage).
Mainwindow.xaml: This grid is the one to which I add my Connectview.xaml UI component
<Grid Grid.Row="0" Name="MainGrid" HorizontalAlignment="Stretch" Style="{DynamicResource styleBackground}" >
</Grid>
I have a listbox in my mainwindow.xaml towards the left side which has list of items(i.e. Voltage, Clock etc). The right side of .xaml has my grid which is shown above(contains the connectview on startup). What I basically need is when I click the item from the listbox, I want to hide the view which is shown on startup(connectview) and make the selecteditem UI component visible.
As mentioned in the beginning I want to make a usercontrol of my classlibrary(VoltageView) visible on clicing "Voltage" item from Listbox.
ListBox in my mainwindow.xaml:
<ListBox Name="ButtonPanel" Style="{DynamicResource styleBanner}" ItemsSource="{Binding BoardOperations, Mode=TwoWay}" SelectedItem="{Binding SelectedList}" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Name="BoardTabChanger" Margin="53,27,0,0" Text="{Binding ListName}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The ViewModel Class of MSPBoardControl is here:
public class BoardControlViewModel : INotifyPropertyChanged
{
public ObservableCollection<BoardControlModel> m_BoardOperation;
public List<ConnectModel> m_BoardNames;
public BoardControlViewModel()
{
//Adding items to ListBox
}
public List<ConnectModel> BoardNames
{
get; set;
}
private ConnectModel m_SelectedBoardItem;
public ConnectModel SelectedBoard
{
get; set;
}
public ObservableCollection<BoardControlModel> BoardOperations
{
get; set;
}
//GIVES ME THE SELECTED ITEM FROM LISTBOX
private BoardControlModel m_SelectedListItem;
public BoardControlModel SelectedList
{
get
{
return m_SelectedListItem;
}
set
{
m_SelectedListItem = value;
onListSelected(value);
NotifyPropertyChanged("SelectedList");
}
}
private void onListSelected(BoardControlModel SelectedItem)
{
if (SelectedItem.ListName == "Voltage")
{
//HOW CAN I ADD THE VOLTAGEVIEW HERE???
}
}
The above method OnListSelected() retrieves the selecteditem and when I check the condition dat item = voltage, I want to add the voltageview component to my maingrid and hide the connectview.
VoltageView.xaml:
<Grid Name="VoltageControl" Style="{DynamicResource styleBackground}" DataContext="{StaticResource VoltageViewModel}" >
<Label Content="Label" FontSize="15" Foreground="Black" Height="28" HorizontalAlignment="Left" Margin="10,31,0,0" Name="label9" VerticalAlignment="Top" Width="117" />
<Label Content="Set Voltage" FontSize="15" Foreground="Black" Height="28" HorizontalAlignment="Left" Margin="150,31,0,0" Name="label10" VerticalAlignment="Top" Width="117" />
<Label Content="Current Value" FontSize="16" Foreground="Black" Height="28" HorizontalAlignment="Left" Margin="300,31,0,0" Name="label11" VerticalAlignment="Top" Width="117" />
<Label Content="Enable/Disable" FontSize="15" Foreground="Black" Height="28" HorizontalAlignment="Left" Margin="460,31,0,0" Name="label12" VerticalAlignment="Top" Width="127" />
<Button Content="Refresh All" FontSize="13" Height="25" HorizontalAlignment="Left" Margin="230,520,0,0" Name="RefreshBtn" VerticalAlignment="Top" Width="105" />
</Grid>
Please help!!!
I'm going to keep this general(ish). Your best bet is probably to add a property on BoardControlViewModel of type Grid (or whatever UIElement that is common to voltage and connect). As the selection changes, change the UI property to the view you want and hit NotifyPropertyChanged for that property. You can call NotifyPropertyChanged right from onListSelected if you like. Bind the content of whatever grid you're using to this property, prefferably right in the xaml. When the property changes, WPF will notify the grid that its bound content has changed and it will query your new property to see what it should be.

How to get TextBox inside DataTemplate in a ListBox to notify the ViewModel on value change

What I need to find is when a textbox's value is changing or the dropdown's value changes inside my datatemplate item, I need to be notified in my ViewModel.cs.
So basically as a user edits a textbox inside the listbox, the viewmodel will be notified as the values are changing.
The reason is I need to go through all my Entries and update something as items inside the listbox's datatemplate change.
Any suggetion?
I have the following in my XAML.
<ListBox x:Name="EntriesListBox"
ItemsSource="{Binding Path=Entries}"
Grid.Row="1">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<ComboBox x:Name="EntriesPropertyName"
Width="215"
Margin="0,0,5,0"
SelectedItem="{Binding Path=Property, Mode=TwoWay}"
ItemsSource="{Binding Source={StaticResource DataContextProxy},Path=DataSource.EntityTypeProperties}" />
<TextBox x:Name="EntriesPropertyValue"
Width="215"
Margin="0,0,5,0"
Text="{Binding Path=Value, Mode=TwoWay, BindsDirectlyToSource=True}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The following is in my VM (ViewModel.cs)
public ObservableCollection<Entry> Entries { get; set; }
The following is in my business object (Entry.cs)
public class Entry
{
public PropertyItem Property { get; set; }
public string Value { get; set; }
}
On your binding, set the UpdateSourceTrigger... Also implement INotifyPropertyChanged
Provided that you have setup your view model class properly (by implementing INotifyPropertyChanged), following is what you may want to do:
<TextBox x:Name="EntriesPropertyValue"
Width="215"
Margin="0,0,5,0"
Text="{Binding Path=Value, Mode=TwoWay, BindsDirectlyToSource=True, UpdateSourceTrigger=PropertyChanged}" />
This seems to work. Any reason not to do it this way?
private void EntriesPropertyValue_TextChanged(object sender, TextChangedEventArgs e)
{
(sender as TextBox).GetBindingExpression(TextBox.TextProperty).UpdateSource();
this.ViewModel.UpdateFinalQuery();
}

Solution for filtering all instances of an object?

I'm wondering if I can do something like this with CollectionViewSource too. I have a DataTemplate that looks like this:
<DataTemplate DataType="{x:Type local:MyObject}">
<StackPanel Orientation="Horizontal">
<Grid>
<Image Source="Images\gear16.png" />
<Image Source="Images\disk.gif" HorizontalAlignment="Right" VerticalAlignment="Bottom"
Visibility="{Binding MyProp, Converter={StaticResource BooleanToVisibilityConverter}}" />
</Grid>
<TextBlock Margin="5,0,0,0" Text="{Binding Name}" VerticalAlignment="Center" />
</StackPanel>
</DataTemplate>
So of course, everything bound to that type of object takes that DataTemplate, or in other words, every object of type MyObject gets that datasource. Can I do something similar for CollectionViewSource? Make every object of type MyObject go through the filtering methods?
The problem is that I have several instances of this collection oF MyObject, and it will be very difficult to filter one by one (I think), and still handle updates to data and everything, so I'm wondering if there is a solution like this.
Thanks!
You can use CollectionView.Filter property to perform filtering. There's no way for any "group" filtering, only "one by one" as you say. You can read here about filtering.
Your filtering handler will look like this:
private void ShowOnlyBargainsFilter(object sender, FilterEventArgs e)
{
if (e.Item is MyObject)
{
e.Accepted = true;
}
else
{
e.Accepted = false;
}
}
Hope it helps.

Silverlight: Define event handler in hierarchical data template

I am having problems getting at a click event of a button and am using Silverlight 3.0 w/ matching Silverlight Toolkit.
Problem
I have this TreeView:
TreeView sample http://a.imagehost.org/0939/TreeView.png.
The value for a certain node is the sum of the values of its children. Only in leaves can data be added (for the time being).
What I want to achieve is that a user can add (and eventually remove) entries in the tree to eventually create a custom diagram.
To that end I would like the "plus sign" to insert a new line / node as child of the node where the user clicked. (I.e. if I click the plus at "Display", I get a line below to specify CRT or TFT or whatever.)
Thing is, for all my brain is worth, I don't know how to receive any useful event.
The TextBlock, TextBox and Button are defined in a hierarchical template and I can't define a Click-handler in that template.
OTOH, I haven't found a way to get at the template items of a certain TreeViewItem from within (c#) code. Very well am I able to do trv.ItemContainerGenerator.GetContainerFromItem(item), and as Justin Angel showed I can very well change the font size, but didn't find any way to access the textbox or button.
Is there any way to capture the click event to the button? Or any alternative way of getting something that gives the "add below" functionality?
Thank you in advance.
More Data
The treeview xaml is this:
<controls:TreeView x:Name="SankeyDataTree"
ItemTemplate="{StaticResource SankeyTreeTemplate}" BorderThickness="0"
Background="{x:Null}" HorizontalAlignment="Left" VerticalAlignment="Top">
<controls:TreeViewItem IsExpanded="True">
<controls:TreeViewItem.HeaderTemplate>
<DataTemplate>
<TextBlock Text="Loading..."/>
</DataTemplate>
</controls:TreeViewItem.HeaderTemplate>
</controls:TreeViewItem>
</controls:TreeView>
I use this HierarchicalDataTemplate (and stole the appraoch from Timmy Kokke):
<Data:HierarchicalDataTemplate x:Key="SankeyTreeTemplate" ItemsSource="{Binding Children}">
<Grid Height="24">
<Grid.ColumnDefinitions>
<!-- ... -->
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Path=Value.name, Mode=TwoWay}" VerticalAlignment="Center"/>
<TextBox Text="{Binding Path=Value.flow, Mode=TwoWay}" Margin="4,0" VerticalAlignment="Center" d:LayoutOverrides="Width" Grid.Column="1" TextAlignment="Right" Visibility="{Binding Children, Converter={StaticResource BoxConverter}, ConverterParameter=\{box\}}"/>
<TextBlock Text="{Binding Path=Value.throughput, Mode=TwoWay}" Margin="4,0" VerticalAlignment="Center" d:LayoutOverrides="Width" Grid.Column="1" TextAlignment="Right" Visibility="{Binding Children, Converter={StaticResource BoxConverter}, ConverterParameter=\{block\}}"/>
<Button Margin="0" Grid.Column="2" Style="{StaticResource TreeViewItemButtonStyle}">
<Image Source="../Assets/add.png" Margin="0" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Button>
</Grid>
</Data:HierarchicalDataTemplate>
To this TreeView is bound a "SimpleTree", whose Values hold basically onle a string (name) and two doubles (flow and throughput).
public String name { get; set; }
public Double flow { get; set; }
public Double throughput { get; set; }
(Plus the code for the INotifyPropertyChanged to get a twoway bind to the text boxes.)
You can attach a Behavior to the Button in the HierarchicalDataTemplate and let that handle Click events from the Button.
Download and install the Expression Blend 3 SDK. Add a reference to System.Windows.Interactivity in the project and add a Behavior attached to a Button:
public class ButtonClickBehavior : Behavior<Button> {
protected override void OnAttached() {
base.OnAttached();
AssociatedObject.Click += ButtonClick;
}
protected override void OnDetaching() {
base.OnDetaching();
AssociatedObject.Click -= ButtonClick;
}
void ButtonClick(object sender, RoutedEventArgs e) {
Node node = AssociatedObject.DataContext as Node;
if (node != null) {
// Button clicked. Do something to associated node.
}
}
}
Attach the Behavior to the Button in the HierarchicalDataTemplate (assuming this namespace declaration: xmlns:interactivity="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"):
<Button Margin="0" Grid.Column="2" Style="{StaticResource TreeViewItemButtonStyle}">
<Image Source="../Assets/add.png" Margin="0" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<interactivity:Interaction.Behaviors>
<local:ButtonClickBehavior/>
</interactivity:Interaction.Behaviors>
</Button>
If desired you can add properties to ButtonClickBehavior and set those from XAML to create a more reusable Behavior.
You can handle the button click event in the code behind. To get to the data, just bind it to the Tag attribute.
<Button Margin="0" Grid.Column="2"
Click="Button_Click" Tag="{Binding}"
Style="{StaticResource TreeViewItemButtonStyle}">
<Image Source="../Assets/add.png" Margin="0"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Button>
In the code behind, handle it and access the element.
private void Button_Click(object sender, RoutedEventArgs e)
{
var data = ((Button)sender).Tag as SimpleTreeNode
}
Where SimpleTreeNode is the name of your tree element class.
You should be able to add a new node to the data found now.

Resources