|
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.
|
|
|
|
|
Manually set the Background, BorderBrush and BorderThickness in Generic.xaml file and boom - it shows up.
Why does the TemplateBinding not work in code behind?
|
|
|
|
|
sorry for confusing with UserControl, it should of course be custom control.
|
|
|
|
|
|
thanks for the link! that's a good example of a user control! I like the adorner solution.
|
|
|
|
|
Failure by design:
You can move the splitter between green and red down out of the window and no chance to get it back. Will be hard to fix this.
|
|
|
|
|
Thanks for the bug message. Yes, this can be a problem for the user. It seems like also the GridSplitter control has the same problem. I added some code that constrains the size of the element being resized to the available client area - this solves the problem for the demo application, but not in general. It would be great to hear if you have some ideas how to solve this. Maybe it is neccessary to use the Measure/Arrange methods?
|
|
|
|
|
Is there any way to make it look more like a typical splitter control with a thin beveled border and perhaps a gripper?
Thanks, I love this control! DockPanel is so much cleaner than Grid for form layout.
|
|
|
|
|
hi Chris, thanks for the comments! I think this control should be changed to a look-less custom control, and the beveled border and gripper can then be implemented as a template. I will post an update later!
|
|
|
|
|
This is because most of the time some panel you want to resize proportially and others no
Also, are min-max height width for each docked panel will work with your control? (to limit the proportionnal resizing for example)
|
|
|
|