WPF Custom "Toolbox" Selector control - c#

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.

Related

How to add a border around the entire DataGrid column header?

I've customized the DataGrid a lot, but surprisingly I'm unable to add a border around the entire header area without it breaking.
So this is what I'm trying to accomplish:
The following control is responsible for displaying the header area: DataGridColumnHeadersPresenter.
I added a border around it, and as you can see from the screenshot above, it does work, but the problems begin only when the grid is empty! (this means that also the empty row needs to be removed, which can be done by setting CanUserAddRows="False").
So far, here's my style:
<Border BorderBrush="Red" BorderThickness="1" Grid.Column="1">
<DataGridColumnHeadersPresenter x:Name="PART_ColumnHeadersPresenter" Margin="0,0,0,5"
Visibility="{Binding HeadersVisibility, ConverterParameter={x:Static DataGridHeadersVisibility.Column}, Converter={x:Static DataGrid.HeadersVisibilityConverter}, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}">
</DataGridColumnHeadersPresenter>
</Border>
All I did was wrap it with a border, but now when I apply filtering so that the grid is empty, the header disappears to the right and the application slows down to a crawl.
Demonstration:
If I remove the border, everything works as expected. It seems like the DataGrid is expecting a very specific tree structure, otherwise it just explodes.
I tried changing the template of the DataGridColumnHeadersPresenter, but also that expects a very specific structure which looks like this:
<DataGridColumnHeadersPresenter.Template>
<ControlTemplate TargetType="{x:Type DataGridColumnHeadersPresenter}">
<Grid>
<DataGridColumnHeader x:Name="PART_FillerColumnHeader"
IsHitTestVisible="False" />
<ItemsPresenter />
</Grid>
</ControlTemplate>
</DataGridColumnHeadersPresenter.Template>
If I give the DataGridColumnHeader a BorderBrush and BorderThickness, it doesn't look right, and if I add my own border control anywhere, the same problem arises.
The DataGridColumnHeadersPresenter actually has BorderBrush and BorderThickness properties, but they have no effect at all.
One workaround I found was to set Grid.Column to 0 so that it goes in place of the row headers column header, then just set HeadersVisibility="Column" on the DataGrid so it doesn't look broken, and the problem disappears. Unfortunately I need the row headers, so this is an unacceptable solution.
Default style for DataGrid can be found here, or just right-click it in Visual Studio and go to Edit Template, then Edit a Copy, which is what I did.
There must be an easy way to accomplish this that I'm probably just not seeing right now...
Ok so I just returned to tackle this problem after #jsanalytics pointed out the existence DataGridHeaderBorder, and by analyzing the default tree structure more deeply, with a little bit of trial and error, I managed to get the job done.
I did not want the DataGridHeaderBorder though, which is part of the Windows themes, but replaced it with a regular border.
My implementation:
<DataGridColumnHeadersPresenter Grid.Column="1" x:Name="PART_ColumnHeadersPresenter"
Visibility="{Binding HeadersVisibility, ConverterParameter={x:Static DataGridHeadersVisibility.Column}, Converter={x:Static DataGrid.HeadersVisibilityConverter}, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}">
<DataGridColumnHeadersPresenter.Template>
<ControlTemplate TargetType="{x:Type DataGridColumnHeadersPresenter}">
<Grid HorizontalAlignment="Left">
<ItemsPresenter />
<DataGridColumnHeader x:Name="PART_FillerColumnHeader" IsHitTestVisible="False">
<DataGridColumnHeader.Template>
<ControlTemplate TargetType="{x:Type DataGridColumnHeader}">
<Grid>
<Border BorderThickness="2" BorderBrush="Red">
<ContentPresenter RecognizesAccessKey="True"
SnapsToDevicePixels="True" />
</Border>
<!--Uncomment if you need these resizing grippers-->
<!--<Thumb x:Name="PART_LeftHeaderGripper" HorizontalAlignment="Left" />
<Thumb x:Name="PART_RightHeaderGripper" HorizontalAlignment="Right" />-->
</Grid>
</ControlTemplate>
</DataGridColumnHeader.Template>
</DataGridColumnHeader>
</Grid>
</ControlTemplate>
</DataGridColumnHeadersPresenter.Template>
</DataGridColumnHeadersPresenter>
You'll probably need to tweak it for your own needs, but this fills our requirements :-)

WPF Listbox items with name on mouseover or active

I've tried several different solutions to this, but can't land on one that meets all of my needs.
We have an observable collection of objects that each have a status and a name. It's a sort of task-list of running items. To display this list in WPF, we have some code that represents each item as an ellipse with some colors and animations.
The problem is that we want to display the name of the item as a 'popup' both on mouseover, or when the task is in a given state.
Attempt #1
My first attempt implemented this as a Datatemplate (to be used as an ItemTemplate) with an actual WPF Popup. I implemented two datatriggers - one for mouseover and one for task state. I positioned the popup based on my ellipse and everything was great. However, moving the window or switching to a different window left the popup on top of everything.
Attempt #2
Instead of using the popup I used a textbox in a canvas. This works great until the Datatemplate is used in the Listbox. The item host (stackpanel) ends up clipping the string.
Here's example code:
<DataTemplate x:Key="EllipseTemplate">
<Grid Height="40" Width="40">
<Canvas Name="PopupCanvas" HorizontalAlignment="Center" Width="500">
<TextBlock Name="PopupName"
Width="{Binding ElementName=PopupCanvas, Path=ActualWidth}"
Text="{Binding}"
Background="Transparent"
FontSize="16" HorizontalAlignment="Center" TextAlignment="Center" FontWeight="Bold"
Canvas.Top="-25"
Visibility="Collapsed"
/>
</Canvas>
<Ellipse x:Name="Ellipse" Height="25" Width="25" Margin="0"
HorizontalAlignment="Center" VerticalAlignment="Center"
Fill="Green"
RenderTransformOrigin="0.5, 0.5" StrokeThickness="0.5" Stroke="Black">
</Ellipse>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsMouseOver, RelativeSource={RelativeSource TemplatedParent}}" Value="True">
<Setter TargetName="PopupName" Property="Visibility" Value="Visible" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
<Grid Name="Test" Background="LightGoldenrodYellow" ClipToBounds="False" Margin="50">
<ListBox Name="OverlayTest"
Background="CornflowerBlue"
BorderThickness="0"
VerticalAlignment="Center"
Margin="10"
ClipToBounds="False"
ItemTemplate="{StaticResource EllipseTemplate}">
<sys:String>Very long string that will get clipped</sys:String>
<sys:String>Two</sys:String>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" IsItemsHost="True" Margin="10,50,10,50" ClipToBounds="False"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</Grid>
Attempt #3
I moved my canvas/textbox outside of the datatemplate and create a grid to put it above the listbox of ellipses. This works from a layout perspective, but creates a big mess in terms of checking for mouseover and centering the textbox on the control that's active/hovered.
So that leaves me without an implementation that works the way I want. Anyone have any suggestions?
Ok here I have another idea. I had problem with the ListBox before. Try replacing the ListBox with an ItemsControl.
Attempt #2 sounds like is working fine. You should be able to solve the issue of the clipping using one of these solutions (or all of them):
Set the ClipToBounds property of the ListBox to false
Set the ClipToBounds property of the Stackpanel to false

Two children to a border

I've created a border and inside it I put a combox. After that, I also added a checkbox but then VS complained that there's only one child property allowed. It's possible that I'm asking the wrong question.
Should I use a different control for "keeping stuff together"? Which one would that be?
If the border is correct for the purpose, what am I doing incorrectly, then?
<Border BorderBrush="Black" BorderThickness="1" ... >
<ComboBox x:Name="comboBox" ... />
<CheckBox x:Name="checkBox" ... />
</Border>
Border is a fine control to use if you want a border around your content, but its not a Panel, so it can only have one child. Simply do something like:
<Border BorderBrush="Black" BorderThickness="1" ... >
<StackPanel>
<ComboBox x:Name="comboBox" ... />
<CheckBox x:Name="checkBox" ... />
</StackPanel>
</Border>
Now the Border only has one child, and your other elements are laid out by a Panel element which can have multiple children.

Wpf combobox with checkboxes in it

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).

Difference between Templates

What is the difference between
ControlTemplate
DataTemplate
HierarchalDataTemplate
ItemTemplate
Control Template
A ControlTemplate specifies the visual structure and visual behavior of a control. You can customize the appearance of a control by giving it a new ControlTemplate. When you create a ControlTemplate, you replace the appearance of an existing control without changing its functionality. For example, you can make the buttons in your application round rather than the default square shape, but the button will still raise the Click event.
An Example of ControlTemplate would be
Creating a Button
<Button Style="{StaticResource newTemplate}"
Background="Navy"
Foreground="White"
FontSize="14"
Content="Button1"/>
ControlTemplate for Button
<Style TargetType="Button" x:Key="newTemplate">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border x:Name="RootElement">
<!--Create the SolidColorBrush for the Background
as an object elemment and give it a name so
it can be referred to elsewhere in the control template.-->
<Border.Background>
<SolidColorBrush x:Name="BorderBrush" Color="Black"/>
</Border.Background>
<!--Create a border that has a different color by adding smaller grid.
The background of this grid is specificied by the button's Background
property.-->
<Grid Margin="4" Background="{TemplateBinding Background}">
<!--Use a ContentPresenter to display the Content of
the Button.-->
<ContentPresenter
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Margin="4,5,4,4" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
More about ControlTemplate
Data Templates
Data Template are a similar concept as Control Templates. They give you a very flexible and powerful solution to replace the visual appearance of a data item in a control like ListBox, ComboBox or ListView. WPF controls have built-in functionality to support the customization of data presentation.
An Example for the DataTemplate would be
<!-- Without DataTemplate -->
<ListBox ItemsSource="{Binding}" />
<!-- With DataTemplate -->
<ListBox ItemsSource="{Binding}" BorderBrush="Transparent"
Grid.IsSharedSizeScope="True"
HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="Key" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
<TextBox Grid.Column="1" Text="{Binding Value }" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
More about DataTemplates and Triggers
Item Templates
You use the ItemTemplate to specify the visualization of the data objects. If your ItemsControl is bound to a collection object and you do not provide specific display instructions using a DataTemplate, the resulting UI of each item is a string representation of each object in the underlying collection.
An Example for Item Template would be
<ListBox Margin="10" Name="lvDataBinding">
<ListBox.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text="Name: " />
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
<TextBlock Text=", " />
<TextBlock Text="Age: " />
<TextBlock Text="{Binding Age}" FontWeight="Bold" />
<TextBlock Text=" (" />
<TextBlock Text="{Binding Mail}" TextDecorations="Underline" Foreground="Blue" Cursor="Hand" />
<TextBlock Text=")" />
</WrapPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
When you set an ItemTemplate on an ItemsControl, the UI is generated as follows (using the ListBox as an example):
During content generation, the ItemsPanel initiates a request for the ItemContainerGenerator to create a container for each data item. For ListBox, the container is a ListBoxItem. The generator calls back into the ItemsControl to prepare the container.
Part of the preparation involves the copying of the ItemTemplate of the ListBox to be the ContentTemplate of the ListBoxItem.
Similar to all ContentControl types, the ControlTemplate of a ListBoxItem contains a ContentPresenter. When the template is applied, it creates a ContentPresenter whose ContentTemplate is bound to the ContentTemplate of the ListBoxItem.
Finally, the ContentPresenter applies that ContentTemplate to itself, and that creates the UI.
If you have more than one DataTemplate defined and you want to supply logic to programmatically choose and apply a DataTemplate, use the ItemTemplateSelector property.
The ItemsControl provides great flexibility for visual customization and provides many styling and templating properties. Use the ItemContainerStyle property or the ItemContainerStyleSelector property to set a style to affect the appearance of the elements that contain the data items. For example, for ListBox, the generated containers are ListBoxItem controls; for ComboBox, they are ComboBoxItem controls. To affect the layout of the items, use the ItemsPanel property. If you are using grouping on your control, you can use the GroupStyle or GroupStyleSelector property.
For more information, see Data Templating Overview.
ControlTemplaes defines the "look" and the "behavour" of a control. A button is rectangular by default. A ListBox has a white background by default. These are all defineed by Control's ControlTemple.
A DataTemplae helps a Control with Layout of Data that it holds. If a list of Users are added to listbox and you would like UserName to show up before UserPassword then you will define this inside a DataTemples. DataTemples is assigned to the ItemTemplate (4) Property of the ListBox.
HierarchalDataTemplte is same as DataTemples except that it deal with Hierarchal Data Source. It is commonlly used with TreeView Control.

Categories