Wpf combobox with checkboxes in it - c#

I am trying to implement a combobox with checkboxes in it. All the articles/resources I found on Google/SO suggest adding a bool to my business object. But I am looking to create a reusable control.
So I created a custom control inherited from combobox and changed the control in the popup with a itemscontrol.
Here is my XAML (for brevity adding just the xaml for popup)
<Popup Name="Popup" Placement="Bottom" IsOpen="{TemplateBinding IsDropDownOpen}" AllowsTransparency="True" Focusable="False" PopupAnimation="Slide">
<Grid Name="DropDown" SnapsToDevicePixels="True" MinWidth="{TemplateBinding ActualWidth}" MaxHeight="{TemplateBinding MaxDropDownHeight}">
<Border x:Name="DropDownBorder" Background="{StaticResource BackgroundBrush}" BorderThickness="1" BorderBrush="{StaticResource BorderBrush}" />
<ScrollViewer Margin="4,6,4,6" SnapsToDevicePixels="True">
<ItemsControl ItemsSource="{Binding ItemsSource,RelativeSource={RelativeSource AncestorType=local:CheckedComboBox}}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding}" x:Name="PART_Checkbox" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
</Popup>
As expected it shows a combobox with checkboxes. But I am not able to figure out how to keep track of the checked items?
I was thinking of listening to checked events but when I tried getting the Checkbox in my code-behind, FindName was returning null.
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
if (this.Template != null)
{
var v = Template.FindName("PART_Checkbox",this);
Debug.Assert(v != null);
}
}
Thanks.

Inherit from ListBox
Bind the CheckBox to ListBoxItem.IsSelected in the template of the items (set it in default style via ItemContainerStyle).
Set SelectionMode to Multiple.
SelectedItems then contains your selection. You may also want to bind your selection area to something like a comma-separated list of the SelectedItems (can be done via a converter for example).

Related

How to fill ContentPresenter by binding

I've a problem to connect.
I started to connect my tabs with a tabcontrol.ressources and it worked to show the text of each tabs.
Then I wanted to had a scroll for my TabItems and it doesn't work, nothing shows in tab... I can't even use tabcontrol.ressources anymore...
<DockPanel>
<Button Background="DarkGoldenrod" Height="Auto" Command="{Binding OpenFlyoutDataCommand}">
<StackPanel>
<materialDesign:PackIcon Kind="ArrowRightBoldCircleOutline" Width="30" Height="30"/>
</StackPanel>
</Button>
<TabControl ItemsSource="{Binding TabEDCWaferData, Mode=TwoWay}"
SelectedItem="{Binding SelectedTabEDCWaferData}">
<!-- Used to create a scroolbar for tabitems -->
<TabControl.Template>
<ControlTemplate TargetType="TabControl">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Hidden" >
<TabPanel Grid.Column="0" Grid.Row="0"
Margin="2,2,2,0" IsItemsHost="true"/>
</ScrollViewer>
<ContentPresenter ContentSource="..."/>
</Grid>
</ControlTemplate>
</TabControl.Template>
<!-- Contains the text in the tab item ! -->
<TabControl.Resources>
<DataTemplate DataType="TabItem">
<DockPanel>
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type TabItem}}, Path=Content}" />
</DockPanel>
</DataTemplate>
</TabControl.Resources>
</TabControl>
</DockPanel>
This is connected to a collection of TabItem, where I've a function to add Items binding to an other button.
private ObservableCollection<TabItem> _TabEDCWaferData;
public ObservableCollection<TabItem> TabEDCWaferData
{
get { return _TabEDCWaferData; }
set
{
_TabEDCWaferData = value;
RaisePropertyChanged("TabEDCWaferData");
}
}
public void AddTabItem(string name)
{
TabItem tab = new TabItem();
tab.Header = name;
tab.Content = "Temporary content";
TabEDCWaferData.Add(tab);
}
I read that I have to use the ContentPresenter, but I don't know how to bind it. I think this is not working with TabItems...
I just want to bind it as I did in the Ressources by using the ContentPresenter.
I hope that I'm clear enough ! Thanks
EDIT : I try to display in the ContentPresenter the selected item tab content that I add in the function `AddTabItem.
With ContentPresenter, most times, this does the job:
<ContentPresenter />
The default ContentSource is "Content". That means it'll look at the Content property of the templated parent and it'll take whatever it finds there for its own content.
But that doesn't help you at all, and you don't have to use ContentPresenter; it's just a convenience. In this case, the content you want to present is SelectedItem.Content, which isn't a valid ContentSource for ContentPresenter. But you can do the same thing with a binding on a ContentControl instead:
<TabControl.Template>
<ControlTemplate TargetType="TabControl">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<ScrollViewer
Grid.Row="0"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Hidden"
>
<TabPanel
Grid.Column="0"
Margin="2,2,2,0" IsItemsHost="true"/>
</ScrollViewer>
<ContentControl
Grid.Row="1"
Content="{Binding SelectedItem.Content, RelativeSource={RelativeSource TemplatedParent}}"
/>
</Grid>
</ControlTemplate>
</TabControl.Template>
TemplateBinding isn't going to work with a Path such as "SelectedItem.Content"; it only accepts names of properties on the templated parent. I fixed your Grid.Row attributes, too.
Also, you may as well delete that DataTemplate for TabItem that you put in TabControl.Resources. That's not what DataTemplate is for; you use DataTemplates to define visual presentations for your viewmodel classes, but TabItem is a control. It already knows how to display itself, and in fact that DataTemplate is being ignored, so it's best not to leave it there; you'll only waste time later on making changes to it and trying to figure out why it's not having any effect. Your TabItems will display correctly without it.
Try something like this ?
<ContentPresenter Content="{TemplateBinding Content}" />
Edit
<ContentPresenter x:Name="PART_SelectedContentHost" ContentSource="SelectedContent" />

My listview items are not showing up when setting a template for it

I have two templates, one for a textbox and one for a listview, both are just used to give them rounded corners instead of the default rectangle. My textbox needed the "ScrollViewer x:Name="PART_ContentHost" line in order to show text, but that doens't work for the listview. If I take out the Template for the listview the example listviewitem (stuff) will show up. Otherwise it won't and I can't see any other items I add in the code behind. What am I missing in the xaml to get this to work?
Here is my xaml below:
<!-- Design Templates to set the borders of the controls-->
<UserControl.Resources>
<ControlTemplate x:Key="TextBoxTemplate" TargetType="TextBox">
<Border BorderBrush="Black" BorderThickness="1,1,1,.5" CornerRadius="7">
<ScrollViewer x:Name="PART_ContentHost" ></ScrollViewer>
</Border>
</ControlTemplate>
<ControlTemplate x:Key="ListViewTemplate" TargetType="ListView">
<Border BorderBrush="Black" BorderThickness=".5,1,1,1" CornerRadius="7">
</Border>
</ControlTemplate>
</UserControl.Resources>
<!-- Controls -->
<Grid Height="270" Width="400">
<StackPanel Width="390">
<TextBox Height="35" Name="InputTextbox" Template="{StaticResource TextBoxTemplate}" VerticalContentAlignment="Center" TextChanged="InputTextbox_TextChanged"></TextBox>
<ListView Height="235" Name="ResultsListView" Template="{StaticResource ListViewTemplate}" SelectionChanged="ResultsListView_SelectionChanged">
<ListViewItem Content="stuff"></ListViewItem>
</ListView>
</StackPanel>
</Grid>
Your ControlTemplate doesn't have a presenter associated with it. This is why you're not seeing any items. See this page for a working example of how to create a ListView.ControlTemplate:
MSDN: ListView ControlTemplate Example
and here's an updated xaml for your control template:
<ControlTemplate x:Key="ListViewTemplate" TargetType="ListView">
<Border BorderBrush="Black" BorderThickness=".5,1,1,1" CornerRadius="7">
<ScrollViewer>
<ItemsPresenter />
</ScrollViewer>
</Border>
</ControlTemplate>

Treeview is clipping my items

I've got following code. Why are my items always clipped after about 70px
<sdk:HierarchicalDataTemplate x:Key="OptionsTemplate">
<CheckBox IsTabStop="False" Content="{Binding EnumValue}" IsChecked="{Binding IsSelected, Mode=TwoWay}" />
</sdk:HierarchicalDataTemplate>
<sdk:HierarchicalDataTemplate x:Key="EnumOptionsTemplate" ItemsSource="{Binding Values}" ItemTemplate="{StaticResource OptionsTemplate}">
<TextBlock Text="{Binding EnumType}"/>
</sdk:HierarchicalDataTemplate>
<sdk:TreeView x:Name="FilterTreeView" ItemsSource="{Binding GeoObjectFilter}" ItemTemplate="{StaticResource EnumOptionsTemplate}">
<sdk:TreeView.Template>
<ControlTemplate TargetType="sdk:TreeView">
<StackPanel Background="Transparent">
<ItemsPresenter x:Name="TreeItems" />
</StackPanel>
</ControlTemplate>
</sdk:TreeView.Template>
</sdk:TreeView>
Thanks in advance!
Ho yes i had same issue when redefining the TreeView Template. By the way, i'm not sure you need to, since StackPanel is the Default Template. I got my (wrappanel) template to work by setting a style for ItemsPanel rather than redifining it. And i had to define some width also. Seems the only way to make the Treeview to scale is to bind the width.
See the answer i made to my question here :
https://stackoverflow.com/a/8802718/856501

WPF Custom "Toolbox" Selector control

I am trying to create a custom control, which behaves a bit like one of the "rows" in the toolbox in Expression Blend.
When closed, it displays the first of its items, and when the user holds the mouse down over it for a second or so, it "expands" to reveal the other items in a popup excluding the item which is currently selected (the item which was clicked on still remains visible, but is not grouped with the others).
I have managed to make a control which inherits from ContentControl displays one item and then reveals the others when expanded, but the item which is displayed first does not change when one is clicked.
The template is as follows:
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<ContentPresenter Content="{TemplateBinding MainItem}" /> // The item that is seen even when not expanded
<Popup Name="Popup" Placement="Right" IsOpen="{TemplateBinding IsExpanded}" AllowsTransparency="True" Focusable="False" PopupAnimation="Fade">
<Border Name="SubmenuBorder" SnapsToDevicePixels="True" BorderThickness="0" >
<StackPanel Orientation="Horizontal" IsItemsHost="True" /> // The items only seen when expanded
</Border>
</Popup>
</Grid>
At the moment, I have to manually set the MainItem in XAML and it is not a member of the Items property of the ContentControl.
Is there a way to achieve this functionality where all items are part of the Items collection and are automatically not shown when an IsSelected property or something is set, and are then shown where the current MainItem is shown when this is the case?
I tried changing it to a Selector and using a filter on the Items to achieve this and binding the first ContentPresenter to the SelectedItem but it didn't seem to work.
If possible, the order of the items only visible when the control is expanded should be the same as the order in which they are laid out in XAML, with the currently selected item missing.
Thanks
try using the ItemContainerStyle to set the visibility of the selected item to 'Hidden'. You'd need a BoolToVisibilityConverter (you can write one easily or get one from the WPFToolkit) to get the correct value
<ContentPresenter Content="{Binding ElementName=selector, Path=SelectedItem}" />
<ComboBox x:Name="selector">
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="Visibility" Value="{Binding Path=IsSelected, Converter={StaticResource boolToVisibilityConverter}}" />
</Style>
</ComboBox.ItemContainerStyle>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding SomeProperty}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>ComboBoxItem
I managed to solve the problem by using a VisualBrush to create a preview of the item without having to move it within the visual tree.

silverlight template for ListBox and DataGrid when empty?

So are there any available in the SL4?
I need to display some sort of info when no data is presented in the ListBox and/Or DataGrid when they are empty.
If anyone is familiar with any of these and can provide examples or links, I would appreciate it.
Thanks,
Voodoo
I have not tried this my self yet, but you might be interested in the blog post link below, which provides a solution for the DataGrid, which you could probably adapt for ListBoxes as well.
http://subodhnpushpak.wordpress.com/2009/05/18/empty-data-template-in-silverlight-datagrid/
I've come up with a simpler solution for listboxes that works for me in 99% of the cases. Once setup as a resource, all you have to do is change the Tag property on the listbox to get all the functionality to work.
First, I modify the default template of the Listbox to include a new grid and a textbox like so:
Original XAML
<Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="3" Margin="0">
<ScrollViewer x:Name="ScrollViewer" Background="Transparent" BorderBrush="Transparent" BorderThickness="0" Margin="0" Padding="0" TabNavigation="{TemplateBinding TabNavigation}">
<ItemsPresenter Margin="0,0" />
</ScrollViewer>
</Border>
New XAML
<Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="3" Margin="0">
<Grid >
<TextBlock Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Tag}" VerticalAlignment="Center" HorizontalAlignment="Center" Visibility="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=ItemsSource.Count, Converter={StaticResource ListCount2Visibility}}" Foreground="{StaticResource NormalFontBrush}" FontSize="{StaticResource DefaultFontSize}" />
<ScrollViewer x:Name="ScrollViewer" Background="Transparent" BorderBrush="Transparent" BorderThickness="0" Margin="0" Padding="0" TabNavigation="{TemplateBinding TabNavigation}">
<ItemsPresenter Margin="0,0" />
</ScrollViewer>
</Grid>
</Border>
The textblock visibility property is bound to a custom converter entitled ListCount2Visibility which looks like this:
public sealed class ListCount2Visibility : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null && (int)value > 0 )
return "Collapsed";
else
return "Visible";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
The value converter checks if the ItemSource.Count == 0...if it is, it sets the visibility to visible. Otherwise, it collapses it.
The Text property of the new textblock is then bound to the Tag property of the listbox. (This isn't ideal but it is the quickest way to get the text into the control. Obviously this won't work if you use the tag property for other things).
So basically, you set the tag to the message you want to display, and anytime there are no items in the list, the textbox is shown (centered horizontally and vertically). During development, your message will show since the list is empty (assuming now design time datacontext) which makes it nice to visualize the text.
That is really all there is to it.
If you want, you can even bind the tag property of the listbox to your viewmodel to change the text. So you could do things like "loading...." while items are returned from the database and then change it to the "empty list" message after everything loads. (of course busy indicator is probably better)

Categories