WPF pinning option that does not fill height - wpf

I'm looking for a WPF option that shows panels on the sides, and allows you to pin/unpin them. Basically this means a Window, with a main control in the center and multiple different panels on both the left and right sides of this main control. These panels are collapsed by default, with just their headers visible, and if i hover over them they expand out OVER the main control (without displacing the main control), but i also have the option to pin this panel, where it stays permanantly expanded out, this time forcing the main center control to resize.
Now this sounds pretty much like most docking control options, and indeed i've looked at Avalon and MixModes Synergy , but the problem with these options is that their panels fill out the entire height. I want a panel of a specific height to come out when i hover over it, i don't want it to fill out to screen, and i can't really find anything else that does that. Anyone else seen something like this?
Basically my own ideas on how to do this so far involve programmatically moving the panel from one pinned control to another non-pinned control, but this sounds crazy ugly and i'd love alternatives.

Here is a pure XAMl example of how you can achieve Pin/Unpin functionality.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<DockPanel Grid.Column="1">
<Label Content="Main Content Area" FontSize="22"
VerticalAlignment="Center" HorizontalAlignment="Center"/>
</DockPanel>
<StackPanel HorizontalAlignment="Left">
<StackPanel.Style>
<Style>
<Setter Property="Grid.Column" Value="1"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=LeftPinBtn,Path=IsChecked}" Value="True">
<Setter Property="Grid.Column" Value="0"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<ToggleButton Content="Pin/Unpin" x:Name="LeftPinBtn"/>
</StackPanel>
</Grid>
In the above code,
The left panel lies (floats) on the same grid column of the Main content.
When the ToggleButton is checked they jump to their respective
docking grid column.
You may customize this example & include the mouseover events which will show/hide the panels.
Adding a Grid column to the right + HorizontalAlignment="Right"
will give you right panel.
Similarly using Rows (instead of columns) & VerticalAlignment
will add top/bottom pin functionality.

Related

ZIndex for one canvas over an adjacent canvas

I'm trying to render a row of interlocking arrows, like this:
Each arrow represents a step in a workflow, and the color of each arrow is determined by its step's position in the workflow. While the example above has 3 colors (before, current, after), so far I've implemented only 2 colors (before/current, after) and that's fine for the purposes of this question. I've got an IMultiValueConverter to handle those colors.
The workflow steps are represented as a StepStruct which has a Step property (set manually, more on this later), and a VmFunc property which returns the view model for that step. My current IMultiValueConverter uses the index of the step in the list of steps, rather than the actual Step value.
My problem is the arrows. Initially I have each step as a canvas rendering a simple rectangle, and that's easy to get working. But to make the arrow, I've used a PolyLine that I want to position at the right of the canvas, indeed starting at the right of one canvas and overflowing into the next.
I can't get the Panel.ZIndex to work in such a way that a canvas's arrow is visible overflowing into its right-hand neighbor.
My code is pasted below. Of note:
I've bound the Polyline's Panel.Zindex (which I've read affects its parent canvas's z-index) to the StepStruct's Step property, and I've set the Step values so that each step's value is less than the value of the step to its left, which should show left steps over the right step.
I've commented a line setting the Canvas.Left property of each PolyLine. When I uncomment this line (and move it into place), the Polyline indeed moves over to the right side of the canvas, but it's invisible, which I imagine is because it's hidden behind the canvas to its right. (I've confirmed this by changing the PolyLine to start with negative x-values, so you can see the part that's not blocked by the neighboring canvas. This is not pictured.)
I've rendered each canvas's Step as text, and those TextBlocks all have an identical z-index (40) so that they're in front of some PolyLines and behind others, which shows that the PolyLine does have its z-index set right, at least within its own canvas.
<Border Grid.Row="0" BorderBrush="{StaticResource BackgroundBlack}" BorderThickness="2">
<!--Progress bar-->
<ItemsControl ItemsSource="{Binding StepViewModels}" Grid.Row="0" x:Name="progressBar">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="1"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Canvas Height="24">
<Canvas.Style>
<Style TargetType="Canvas">
<Setter Property="Background" Value="{StaticResource BackgroundDarkGrey}"/>
<Style.Triggers>
<DataTrigger>
<DataTrigger.Binding>
<!-- the binding. this part works so I'm omitting the code -->
</DataTrigger.Binding>
<DataTrigger.Value>True</DataTrigger.Value>
<Setter Property="Background" Value="{StaticResource DisabledGrey}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Canvas.Style>
<Polyline Points="0,-1 20,11 0,25" Stroke="{StaticResource BackgroundBlack}" StrokeThickness="2"
Fill="{Binding Background, RelativeSource={RelativeSource AncestorType=Canvas}}"
Panel.ZIndex="{Binding Step}"
/>
<!--Canvas.Left="{Binding ActualWidth, RelativeSource={RelativeSource AncestorType=Canvas}}"-->
<TextBlock Text="{Binding Step}" FontWeight="ExtraBlack" Panel.ZIndex="40" Foreground="Pink" FontSize="20" Canvas.Left="8"/>
</Canvas>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
The result (with the Canvas.Left still commented)
How do I get the arrows to render at the end of one canvas and atop the next, as desired?
(I could of course keep it this way and make it work but the bindings would be very annoying and complicated, needing to bind each arrow to the color of the previous canvas; or rather do some kind of conversion involving IndexOf - 1)
For ZIndex:
Each item is wrapped by a ContentPresenter, this is done by the ItemContainerGenerator inside this ItemsControl instance.
So you need to edit the container to get a new ZIndex(via a ContentPresenter style and a binding for example).
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Panel.ZIndex" Value="{Binding XXX}" />
</Style>
</ItemsControl.ItemContainerStyle>
For the shape:
I would recommend using a Path control with a predefined shape geometry like:
"M 0,0 L30,0 L34,5 L30,10 L0,10 z"
and have a negative margin at the end like Margin="0,0,-4,0"

XAML- How to fix the position of an image relative to the right hand side of the display window?

I am developing an application for which the GUI is being written/ displayed using XAML. I have most of the desired elements displayed on the GUI at the moment, and am now working on the layout- positioning the various elements in particular places.
Most of the controls/ buttons, etc are aligned to the left, and stay 'bound' to the left hand side of the GUI even if I resize the window by dragging its left hand edge further to the left (the icons, etc move to stay their relative distance from the edge of the window).
However, I have one element which I want to align to the right, so that it will stay 'bound' to the right hand side of the GUI- if I resize the window by dragging its right hand edge further to the right, it should move to stay its relative distance from the edge of the window... at the moment, that element is staying at the position that I have drawn it (i.e. 900 pixels from the left), no matter what the size of the window is- so if I resize the width of the window to less than 900 pixels, that element cannot be seen.
The XAML markup I have used for the elements that I want bound to the left hand side of the GUI is, for example:
<Button Style="{DynamicResource NoChromeButton}" Click="backBtn_Click" Margin="0, 0, 0, 0" HorizontalAlignment="Left" >
<Image Source="C:\...\abc.png" Height="30" Width="40" />
</Button>
The XAML markup that I am trying to use for the element I want bound to the right hand side of the GUI is:
<Button Style="{DynamicResource NoChromeButton}" Click="referThis" Margin="0, 0, 0, 0" HorizontalAlignment="Right" >
<TextBlock>Button XYZ</TextBlock>
</Button>
However, setting the HorizontalAlignment to Right, doesn't actually move the text button to the right- it's still bound to the left hand side of the display... The only way I have found of moving it towards the right, is to set a large value for its 'x' position, i.e. Margin="900, 0, 0, 0", but then the position is not relative to the size of the window, so if I drag the right hand edge too far to the left, that button is outside the displayed area of the window, so I can't see it...
How I can I set the property of the button so that its position is relative to the right hand edge of the display window?
Edit
All of the elements in question are displayed inside the same <Grid> and <StackPanel> tags, i.e.
<StackPanel>
<Grid ...>
<Button ... HorizontalAlignment="Left">
</Button>
...
<Button ... HorizontalAlignment="Right">
</button>
...
</Grid>
...
</StackPanel>
The basic idea of the following solution is the Grid.
Create a ColumnDefinition with fixed width for each element you want aligned to the left, and a ColumnDefinition with fixed width for each element you want aligned to the right. On the middle, create a ColumnDefinition with a Width="*", so WPF first calculates the size of the other columns, based on their fixed length, and then it fills the remaining space with the middle-column.
Remember that each button has to set the Grid.Column property correctly to declare the column it wants to belong to.
As you resize the Window, the Grid updates its layout and the flexible children. In this case, the only flexible child is the middle-column.
Here's a simple implementation:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<Grid.Resources>
<Style TargetType="{x:Type Button}">
<Setter Property="Width" Value="100"/>
<Setter Property="Height" Value="30"/>
<Setter Property="Margin" Value="10"/>
</Style>
</Grid.Resources>
<Button Grid.Column="0" Content="Button1Left"/>
<Button Grid.Column="1" Content="Button2Left"/>
<Button Grid.Column="3" Content="Button1Right"/>
<Button Grid.Column="4" Content="Button2Right"/>
</Grid>
For more complete tutorials about the Grid control, check this, this and this.
Notice that I've set a simple style for the buttons with certain dimensions and margins only to make clear the result, but you can use your own style.
It would be good to know the container including the button (grid, panel, others?):
Anyway If you put:
<Button HorizontalAlignment="Right"
in
<StackPanel Orientation="Horizontal">
It should work... Check too if there is no contradiction bring by the style...

Unable to drag drop items between bound itemscontrols

iv'e got several bound itemscontrols which all play the role of of drop target
i need to be able to drag drop items between these items controls .
the problem is that the items control's are not recognized as drop targets by the drag drop framework
The ItemsControl Panel :
<ItemsPanelTemplate x:Key="TopPipePanelTemplate">
<StackPanel></StackPanel>
</ItemsPanelTemplate>
The DataTemplate :
<DataTemplate x:Key="PipeDataItem" >
<Ellipse Width="45" Height="45" Fill="{Binding IsMine,Converter={StaticResource MyCheckerOwnerToColorConverter}}"></Ellipse>
</DataTemplate>
The ItemsControl Style :
<Style TargetType="{x:Type ItemsControl}" x:Key="ItemsControlStyle">
<Setter Property="ItemTemplate" Value="{StaticResource PipeDataItem}"></Setter>
<Setter Property="AllowDrop" Value="True"></Setter>
<EventSetter Event="Drop" Handler="ItemsControlDropTarget"></EventSetter>
<EventSetter Event="PreviewMouseLeftButtonDown" Handler="ItemsControl_MouseLeftButtonDown"></EventSetter>
</Style>
** for this example lets take 2 of The ItemsControls :**
<ItemsControl ItemsSource="{Binding Path=Pipes[23].Checkers}" Style="{StaticResource ItemsControlStyle}"/>
<ItemsControl Grid.Column="1" ItemsSource="{Binding Path=Pipes[22].Checkers}" Style="{StaticResource ItemsControlStyle}"/>
when regularly displayed :
the empty ( Left ) one is not recognized as a drop target although it as AllowDrop set to True and
handles the Drop event ( as do all the itemscontrols in this screen , look at ItemsControl Style above )
now when i color the itemscontrol panel it s suddenly is recognized :
<ItemsPanelTemplate x:Key="TopPipePanelTemplate">
<StackPanel Background="AliceBlue"></StackPanel>
</ItemsPanelTemplate>
as if now it takes up the entire cell which it occupies ..
iv'e tried setting the panel to VerticalAlignment="Stretch" but this also had no affect
i'm trying to understand why my itemcontrols are not recognized as drop enabled ,
even though i expect that they take up that space , in addition the itemscontrol with the ellipses
is only recognized until the height of the ellipses that occupie it's content .
any ideas ?
just to clarify what i mean by Recognized as drop target is when you drag the ellipse you are able
to drop it on top of the itemscontrol .
for now iv'e just set the background as Transparent
The default background color of a panel doesn't exist, which means hit tests pass through it. To get it to register hit tests, such as mouse over events, you need to give it a background color. Usually I just use White, although you said Transparent also works and would be a better choice.
In addition, StackPanels will usually only take up the space they need. You might be better off using a Panel that stretches to fill all available space, such as a DockPanel with LastChildFill="False", and set DockPanel.Dock="Top" on your items

How can I specify a margin as a percentage?

I have just started using WPF. I'm getting my head around styling system since. I come from a CSS background and I would like to set margin in percentage.
<Style TargetType="TextBlock" x:Key="workflowNameTextBlock">
<Setter Property="Margin" Value="50"/>
</Style>
Currently value is set in pixels, but I would like to set it in %, i.e. 50%.
How can I achieve this?
Here's how you implement 20% left and right margins in WPF:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="6*"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="1" Text="Hello, world."/>
</Grid>
This may seem ridiculously verbose if what you're trying to do is re-implement a simple CSS layout in WPF, but that's because implementing simple CSS layouts is really not the problem space WPF is designed around.
Instead of using Margin, you can do this via a Grid.
Just place your control within a Grid element, and use 3 columns and 3 rows. The column/row sizing can be done as percentages of the containing element.

Synchronizing WPF control widths in a WrapPanel

I have this case
<WrapPanel>
<CheckBox>Really long name</CheckBox>
<CheckBox>Short</CheckBox>
<CheckBox>Longer again</CheckBox>
<CheckBox>Foo</CheckBox>
<Slider MinWidth="200" />
</WrapPanel>
I want all the CheckBoxes inside the WrapPanel to be the same width.
Adding the following almost accomplishes the desired effect
<WrapPanel.Resources>
<Style TargetType="CheckBox" BasedOn="{StaticResource {x:Type CheckBox}}">
<Setter Property="MinWidth" Value="75" />
</Style>
</WrapPanel.Resources>
However, I do not want to hardcode a specific width, rather let the largest CheckBox set the width (the above also fails if any width > 75).
The Slider is independent and should be allowed to be larger than the CheckBoxes.
I do not want to use a Grid (with IsSharedSizeScope) since I do not want a hardcoded number of columns.
This article presents an interesting solution, but it would be nice to solve the problem without creating a custom control or using C# code.
What is the best way to do this, preferrably in XAML only?
I originally looked at this using IsSharedSizeGroup but hit a roadblock with making it dynamically apply to things instead of explicitly wrapping items. In this case, creating an AttachedProperty in code or another code based solution may in the long run be better then a XAML only approach. However, to create a purely XAML solution we can make use of the SharedSizeGroup property on a ColumnDefinition to share the sizes of each element and then use set the IsSharedSizeScope property on the WrapPanel. Doing so will make all of the contents in the WrapPanel with the same SharedSizeGroup share their width for columns and height for rows. To wrap the ComboBoxes and possibly ComboBoxes that are not currently in the XAML but will be added to the WrapPanel, we can create a Style and re-template the ComboBox to bassicly wrap it with a Grid.
<WrapPanel Grid.IsSharedSizeScope="True">
<WrapPanel.Resources>
<Style TargetType="{x:Type CheckBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CheckBox}">
<Grid Background="LightBlue">
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="WrapPannelGroup" />
</Grid.ColumnDefinitions>
<CheckBox Style="{x:Null}"
IsChecked="{TemplateBinding IsChecked}">
<!--Other TemplateBindings-->
<CheckBox.Content>
<ContentPresenter />
</CheckBox.Content>
</CheckBox>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</WrapPanel.Resources>
<CheckBox>Really long name</CheckBox>
<CheckBox>Short</CheckBox>
<CheckBox IsChecked="True">Longer again</CheckBox>
<CheckBox>Foo</CheckBox>
<Slider MinWidth="200" />
</WrapPanel>
Here we are re-templating all CheckBoxes without a style inside the WrapPannel to instead be CheckBoxes surrounded by a Grid. However, because of this we need to re-bind all of the CheckBoxes properties that we want to maintain. While that could become burdensome, it also allows for a pure XAML approach.
You can add a property or a converter that does the needed work, then bind each column's width to it. The property or converter can access the entire list of items, finding the widest one, and returning the desired width for all elements.
The best way to do this is to use a CustomControl like the article you posted.
Any solution you come across is going to have to iterate through the list of items and find the maximum width during the measure phase.
Any sort of XAML-only answer would have to be provided OOTB (e.g. IsSharedSizeScope), or would leverage some sort of multi-binding to link the items together. Thus any sort of XAML answer would be full of markup which makes it more verbose (and less elegant).
The only modification that I see to the CodeProject article you posted is adding the ability to "turn-off" consideration of certain elements (like your slider). This could be done as an additional attached property.

Resources