|
First off, let me same that this is a great project and works very well. However, I noticed that the top/bottom and left/right splitters act slightly differently.
For example, if I move the splitter for the yellow area and drag it to the left, it only goes as far as the light blue area, but if I move the splitter for the light blue area to the right, it can go over the yellow area all the way to the right edge of the window. This is the same for the light green and light pink areas. I can only drag the light pink area up to where the light green area is, but I can drag the light green area down over the light pink area to the bottom edge of the screen.
Is there a way to change this so if I drag the light blue area to the right (or the light green area down) it will stop at the top edge of the yellow area (or the top of the pink area)? I went through the code and XAML but I'm not understanding why this behavioral difference exists.
Thanks in advance for any help you can provide on this.
A black hole is where God tried to divide by zero.
There are 10 kinds of people in the world; those who understand binary and those who don't.
|
|
|
|
|
Exactly what I was looking for. Worked out of the box.
Great!
|
|
|
|
|
Iam getting an error which says 'DockPanelSplitter' does not exist in XML namespace 'clr-namespace:DockPanelSplitter'
I have included DockPanelSplitter Project into my solution and hence added a reference of DockPanelSplitter Project into my project.
But then to, Iam getting above error .
Plz help me out with some solution. Thanks.
|
|
|
|
|
Thank you for this. However, I needed to use this with an expander, so I modified the control by (converting to vb) adding:
Private _elementName As String
Public Shared ReadOnly ElementNameProperty As DependencyProperty = DependencyProperty.Register("ElementName", GetType(String), GetType(DockPanelSplitter), New UIPropertyMetadata(Nothing, AddressOf OnElementNameChanged))
Public Property ElementName As String
Get
Return DirectCast(GetValue(ElementNameProperty), String)
End Get
Set(value As String)
SetValue(ElementNameProperty, value)
End Set
End Property
Private Shared Sub OnElementNameChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
DirectCast(d, DockPanelSplitter)._elementName = e.NewValue
End Sub
and then modifying
Private Sub UpdateTargetElement()
Dim dp As Panel = TryCast(Parent, Panel)
If dp Is Nothing Then
Return
End If
Dim i As Integer = dp.Children.IndexOf(Me)
If i > 0 AndAlso dp.Children.Count > 0 Then
element = TryCast(dp.Children(i - 1), FrameworkElement)
Dim p = TryCast(dp.Children(i - 1), FrameworkElement)
If Not String.IsNullOrEmpty(Me._elementName) AndAlso p IsNot Nothing Then
element = p.FindName(Me._elementName)
End If
End If
End Sub
This was my quick and dirty fix for that need.
|
|
|
|
|
Hi Objo this is an excellent control, I was searching for something like this. I have a doubt respecting the use of the control inside an ItemsControl. Let me try to explain my request:
I need yo visualize multiple datagrids inside a dockpanel and between each Dockpanel I need a Gridsplitter functionality. I try to use you control, but I can not resize or move the splitter.
Could any help me with this problem?
Thanks
|
|
|
|
|
Excellent control, very useful, worked for me without modification. Thanks very much.
|
|
|
|
|
|
Here's my scenario. I need a control with two "panes" that each bind to an IsVisible property. Both panes, only pane 1, only pane 2, or neither pane can be displayed. And I need the relative size of the panes to be adjustable (via a splitter drag action) by the user. The problem that I'm experiencing with the DockPanel control is that the DockPanel.Dock attached property doesn't have an explicit Fill state. And also the DockPanel has this weird behavior that even if the DockPanel only contains one non-collapsed UIElement (Visibility="Visible") and that element doesn't even have an explicitly set DockPanel.Dock attached property and the DockPanel's LastChildFill property is set to True, rather that expanding the non-collapsed UIElement to fill the DockPanel, it instead defaults to left-docking. Go figure! To be more clear, this behavior occurs when there exist additional collapsed elements beyond the one non-collapsed UIElement. Hence, when you collapse the second pane, the first pane doesn't expand to fill the DockPanel.
So I'm curious if there might be an easy way to tweak your control to achieve this behavior?
Many thanks!
|
|
|
|
|
Hi CDCii,
please create a small example application and we can see if we can get the dock panel splitter control to work correctly with it. The control is included in propertytools.codeplex.com, you can continue the discussion there. If the question is about DockPanel behaviour and not the DockPanelSplitter, a question on stack overflow may also be a good idea!
|
|
|
|
|
Painless and easy to implement. Does the job well.
|
|
|
|
|
any advice for creating support for this in silverlight?
|
|
|
|
|
Modified to run under silverlight, but it still need work.
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace Silverlight3dLib1
{
public partial class DockPanelSplitter2 : UserControl
{
//!//static DockPanelSplitter2()
//!//{
//!!//DefaultStyleKeyProperty.OverrideMetadata(typeof(DockPanelSplitter),
//!!// new FrameworkPropertyMetadata(typeof(DockPanelSplitter)));
// override the Background property
//!// BackgroundProperty.OverrideMetadata(typeof(DockPanelSplitter), new FrameworkPropertyMetadata(Brushes.Transparent));
// override the Dock property to get notifications when Dock is changed
//!// DockPanel.DockProperty.OverrideMetadata(typeof(DockPanelSplitter),
//!// new FrameworkPropertyMetadata(Dock.Left, new PropertyChangedCallback(DockChanged)));
//!//}
///
/// Resize the target element proportionally with the parent container
/// Set to false if you don't want the element to be resized when the parent is resized.
///
public bool ProportionalResize
{
get { return (bool)GetValue(ProportionalResizeProperty); }
set { SetValue(ProportionalResizeProperty, value); }
}
public static readonly DependencyProperty ProportionalResizeProperty =
DependencyProperty.Register("ProportionalResize", typeof(bool), typeof(DockPanelSplitter2),
new PropertyMetadata(true));
///
/// Height or width of splitter, depends of orientation of the splitter
///
public double Thickness
{
get { return (double)GetValue(ThicknessProperty); }
set { SetValue(ThicknessProperty, value); }
}
public static readonly DependencyProperty ThicknessProperty =
DependencyProperty.Register("Thickness", typeof(double), typeof(DockPanelSplitter2),
new PropertyMetadata(4.0, ThicknessChanged));
#region Private fields
private FrameworkElement element; // element to resize (target element)
private double width; // current desired width of the element, can be less than minwidth
private double height; // current desired height of the element, can be less than minheight
private double previousParentWidth; // current width of parent element, used for proportional resize
private double previousParentHeight; // current height of parent element, used for proportional resize
#endregion
public DockPanelSplitter2()
{
Loaded += DockPanelSplitterLoaded;
Unloaded += DockPanelSplitterUnloaded;
UpdateHeightOrWidth();
}
void DockPanelSplitterLoaded(object sender, RoutedEventArgs e)
{
Panel dp = Parent as Panel;
if (dp == null) return;
// Subscribe to the parent's size changed event
dp.SizeChanged += ParentSizeChanged;
// Store the current size of the parent DockPanel
previousParentWidth = dp.ActualWidth;
previousParentHeight = dp.ActualHeight;
// Find the target element
UpdateTargetElement();
}
void DockPanelSplitterUnloaded(object sender, RoutedEventArgs e)
{
Panel dp = Parent as Panel;
if (dp == null) return;
// Unsubscribe
dp.SizeChanged -= ParentSizeChanged;
}
private static void DockChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((DockPanelSplitter2)d).UpdateHeightOrWidth();
}
private static void ThicknessChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((DockPanelSplitter2)d).UpdateHeightOrWidth();
}
private void UpdateHeightOrWidth()
{
if (IsHorizontal)
{
Height = Thickness;
Width = double.NaN;
}
else
{
Width = Thickness;
Height = double.NaN;
}
}
public bool IsHorizontal
{
get
{
Dock dock = DockPanel.GetDock(this);
return dock == Dock.Top || dock == Dock.Bottom;
}
}
///
/// Update the target element (the element the DockPanelSplitter works on)
///
private void UpdateTargetElement()
{
Panel dp = Parent as Panel;
if (dp == null) return;
int i = dp.Children.IndexOf(this);
// The splitter cannot be the first child of the parent DockPanel
// The splitter works on the 'older' sibling
if (i > 0 && dp.Children.Count > 0)
{
element = dp.Children[i - 1] as FrameworkElement;
}
}
private void SetTargetWidth(double newWidth)
{
if (newWidth < element.MinWidth)
newWidth = element.MinWidth;
if (newWidth > element.MaxWidth)
newWidth = element.MaxWidth;
// todo - constrain the width of the element to the available client area
Panel dp = Parent as Panel;
Dock dock = DockPanel.GetDock(this);
//!// MatrixTransform t = element.TransformToAncestor(dp) as MatrixTransform;
//!// if (dock == Dock.Left && newWidth > dp.ActualWidth - t.Matrix.OffsetX - Thickness)
//!// newWidth = dp.ActualWidth - t.Matrix.OffsetX - Thickness;
element.Width = newWidth;
}
private void SetTargetHeight(double newHeight)
{
if (newHeight < element.MinHeight)
newHeight = element.MinHeight;
if (newHeight > element.MaxHeight)
newHeight = element.MaxHeight;
// todo - constrain the height of the element to the available client area
Panel dp = Parent as Panel;
Dock dock = DockPanel.GetDock(this);
//!//MatrixTransform t = element.TransformToAncestor(dp) as MatrixTransform;
//!//if (dock == Dock.Top && newHeight > dp.ActualHeight - t.Matrix.OffsetY - Thickness)
//!// newHeight = dp.ActualHeight - t.Matrix.OffsetY - Thickness;
element.Height = newHeight;
}
private void ParentSizeChanged(object sender, SizeChangedEventArgs e)
{
if (!ProportionalResize) return;
DockPanel dp = Parent as DockPanel;
if (dp == null) return;
double sx = dp.ActualWidth / previousParentWidth;
double sy = dp.ActualHeight / previousParentHeight;
if (!double.IsInfinity(sx))
SetTargetWidth(element.Width * sx);
if (!double.IsInfinity(sy))
SetTargetHeight(element.Height * sy);
previousParentWidth = dp.ActualWidth;
previousParentHeight = dp.ActualHeight;
}
double AdjustWidth(double dx, Dock dock)
{
if (dock == Dock.Right)
dx = -dx;
width += dx;
SetTargetWidth(width);
return dx;
}
double AdjustHeight(double dy, Dock dock)
{
if (dock == Dock.Bottom)
dy = -dy;
height += dy;
SetTargetHeight(height);
return dy;
}
Point StartDragPoint;
protected override void OnMouseEnter(MouseEventArgs e)
{
base.OnMouseEnter(e);
if (!IsEnabled) return;
Cursor = IsHorizontal ? Cursors.SizeNS : Cursors.SizeWE;
}
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
if (!IsEnabled) return;
if (!IsMouseCaptured)
{
StartDragPoint = e.GetPosition(Parent as UIElement);
UpdateTargetElement();
if (element != null)
{
width = element.ActualWidth;
height = element.ActualHeight;
IsMouseCaptured = true;
CaptureMouse();
}
}
base.OnMouseLeftButtonDown(e);
}
private Boolean IsMouseCaptured = false;
protected override void OnMouseMove(MouseEventArgs e)
{
if (IsMouseCaptured)
{
Point ptCurrent = e.GetPosition(Parent as UIElement);
Point delta = new Point(ptCurrent.X - StartDragPoint.X, ptCurrent.Y - StartDragPoint.Y);
Dock dock = DockPanel.GetDock(this);
if (IsHorizontal)
delta.Y = AdjustHeight(delta.Y, dock);
else
delta.X = AdjustWidth(delta.X, dock);
bool isBottomOrRight = (dock == Dock.Right || dock == Dock.Bottom);
// When docked to the bottom or right, the position has changed after adjusting the size
if (isBottomOrRight)
StartDragPoint = e.GetPosition(Parent as UIElement);
else
StartDragPoint = new Point(StartDragPoint.X + delta.X, StartDragPoint.Y + delta.Y);
}
base.OnMouseMove(e);
}
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
{
if (IsMouseCaptured)
{
IsMouseCaptured = false;
ReleaseMouseCapture();
}
base.OnMouseLeftButtonUp(e);
}
}
}
|
|
|
|
|
Generally, while using grid-splitter we can restrict one splitter to move upto next/previous splitter. In other words, splitter moves in-between two rows/columns only. But here we can move splitter anywhere from left-to-right/top-to-bottom. Can we implement same restriction here?
|
|
|
|
|
When you resize the demo window down to a minimum and then up again, this exception is thrown:
System.ArgumentException was unhandled
Message="\"-4\" not valid for property \"Height\"."
Source="WindowsBase"
StackTrace:
bei System.Windows.DependencyObject.SetValueCommon(DependencyProperty dp, Object value, PropertyMetadata metadata, Boolean coerceWithDeferredReference, OperationType operationType, Boolean isInternal)
bei System.Windows.DependencyObject.SetValue(DependencyProperty dp, Object value)
bei System.Windows.FrameworkElement.set_Height(Double value)
bei OpenSourceControls.DockPanelSplitter.SetTargetHeight(Double newHeight) in DockPanelSplitterDemo\DockPanelSplitter\DockPanelSplitter.cs:Line 215.
|
|
|
|
|
thanks for the bug report! I cannot reproduce this in the demo application, but I understand there must be some problem in the SetTargetHeight/Width methods. I suggest you move the if statements just before the last line where the element's height is set. Please post back if this helps.
|
|
|
|
|
Hi,
I observe the same problem as kiol!
"Moving down the if statements" worked for me
Regards, H
Btw: Great work!!
|
|
|
|
|
I would like to use this cool tool in my code behind - but I can't get it to work.
It compiles and I can hit break points but I just can't "see" it.
If I use it in XAML, it works just fine.
Has anyone run into this or do I just suck
Thanks in advance,
Jason
|
|
|
|
|
OK, further testing - it is there. Just basically has zero width.
Mouse re-size cursor shows up and I can grab it and re-size the DockPanel.
Still stumped as to why it does not display using .cs file, but does putting it directly in XAML file.
|
|
|
|
|
hi Jason! the Thickness property should be used to change the width of the splitter. This should work both for horizontal and vertical splitters. Use the DockPanel.Dock property to set the orientation of the splitter. Let me know if there is a bug in the code, it seems to work here.
|
|
|
|
|
I did set both Thickness and DockPanel.Dock properties.
Works:
<br />
DockPanel dockPanel = (DockPanel) this.Parent;<br />
DockPanelSplitter dps = new DockPanelSplitter();<br />
<br />
<br />
dps.Thickness = 6;<br />
dps.BorderThickness = 6;
dps.Background = Brushes.AliceBlue;
dps.BorderBrush = Brushes.CornflowerBlue;
<br />
<br />
DockPanel.SetDock( dps, Dock.Left );<br />
<br />
dockPanel.Children.Add( dps );<br />
But this displays correctly in the XAML file:
<br />
...<br />
<DockPanel Background="White" Name="MainDockPanel" Grid.Row="1" Margin="0,2,0,-2"><br />
<br />
<TextBlock>Hello World</TextBlock><br />
<osc:DockPanelSplitter DockPanel.Dock="Left" Style="{StaticResource HorizontalBevelGrip}"/><br />
<br />
</DockPanel><br />
...<br />
|
|
|
|
|
Could you update your DockPanelSplitterDemo project to include a code behind example?
Please?
Thanks so much for building such a helpful widget!
Jason
|
|
|
|
|
Fixed:
Window window = Window.GetWindow( this );
DockPanelSplitter dps = new DockPanelSplitter();
dps.Style = (Style) ( window.Resources[ "VerticalBevelGrip" ] );
|
|
|
|
|
|
Thanks, objo, for writing this article: it's a really useful control and a nice demo to go with it.
I had one problem adding it to my own assembly so I thought I'd post the solution here for any other WPF-newbies using it.
I added the DLL to my own project and it worked but then I decided I needed to tweak it a bit so I added a Themes folder, putting generic.xaml into it and a Controls folder, putting DockPanelSplitter.cs into it. When I ran the app, the splitters didn't appear but there were no errors. I moved the mouse pointer over where the splitters should've been and it didn't change to the double-arrow. I discovered that I needed to add an attribute to AssemblyInfo.cs for it to work:
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
) ]
Once I'd added this code, it worked.
|
|
|
|
|
Thanks for the message - and showing your solution! Yes, this is a UserControl and if you don't reference the assembly, you need to copy the template (generic.xaml) and add the ThemeInfo.
|
|
|
|
|