|
//---------------------------------------------------------------------------
//
// Copyright (C) Microsoft Corporation. All rights reserved.
//
//---------------------------------------------------------------------------
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices; // SafeHandle
using System.Security; // [SecurityCritical]
using System.Security.Permissions;
using System.Windows.Automation.Peers; // AutomationPeer
using System.Windows.Controls.Primitives; // ButtonBase
using System.Windows.Input; // MouseButtonEventArgs
using System.Windows.Media; // VisualBrush
using MS.Internal; // DoubleUtil
using MS.Internal.KnownBoxes; // BooleanBoxes
using MS.Win32; // SafeNativeMethods
namespace System.Windows.Controls
{
/// <summary>
/// Defines the different roles of GridViewColumnHeaders
/// </summary>
public enum GridViewColumnHeaderRole
{
/// <summary>
/// The normal header
/// </summary>
Normal,
/// <summary>
/// The floating header (when dragging a header)
/// </summary>
Floating,
/// <summary>
/// The padding header (the very last header in header bar)
/// </summary>
Padding
}
/// <summary>
/// column header of GridView
/// </summary>
#if OLD_AUTOMATION
[Automation(AccessibilityControlType = "Button")]
#endif
[TemplatePart(Name = "PART_HeaderGripper", Type = typeof(Thumb))]
[TemplatePart(Name = "PART_FloatingHeaderCanvas", Type = typeof(Canvas))]
public class GridViewColumnHeader : ButtonBase
#if OLD_AUTOMATION
, IInvokeProvider
#endif
{
//-------------------------------------------------------------------
//
// Constructors
//
//-------------------------------------------------------------------
#region Constructor
static GridViewColumnHeader()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(GridViewColumnHeader), new FrameworkPropertyMetadata(typeof(GridViewColumnHeader)));
_dType = DependencyObjectType.FromSystemTypeInternal(typeof(GridViewColumnHeader));
FocusableProperty.OverrideMetadata(typeof(GridViewColumnHeader), new FrameworkPropertyMetadata(BooleanBoxes.FalseBox));
// hookup property change event.
StyleProperty.OverrideMetadata(typeof(GridViewColumnHeader), new FrameworkPropertyMetadata(new PropertyChangedCallback(PropertyChanged)));
ContentTemplateProperty.OverrideMetadata(typeof(GridViewColumnHeader), new FrameworkPropertyMetadata(new PropertyChangedCallback(PropertyChanged)));
ContentTemplateSelectorProperty.OverrideMetadata(typeof(GridViewColumnHeader), new FrameworkPropertyMetadata(new PropertyChangedCallback(PropertyChanged)));
ContextMenuProperty.OverrideMetadata(typeof(GridViewColumnHeader), new FrameworkPropertyMetadata(new PropertyChangedCallback(PropertyChanged)));
ToolTipProperty.OverrideMetadata(typeof(GridViewColumnHeader), new FrameworkPropertyMetadata(new PropertyChangedCallback(PropertyChanged)));
}
#endregion
//-------------------------------------------------------------------
//
// Public Methods
//
//-------------------------------------------------------------------
#region Public Methods
/// <summary>
/// Called when the Template's tree has been generated
/// </summary>
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
GridViewColumnHeaderRole role = Role;
if (role == GridViewColumnHeaderRole.Normal)
{
HookupGripperEvents();
}
else if (role == GridViewColumnHeaderRole.Floating)
{
// if this is a floating header, try to find the FloatingHeaderCanvas,
// and copy source header's visual to it
_floatingHeaderCanvas = GetTemplateChild(FloatingHeaderCanvasTemplateName) as Canvas;
UpdateFloatingHeaderCanvas();
}
}
#endregion
//-------------------------------------------------------------------
//
// Public Properties
//
//-------------------------------------------------------------------
#region Public Properties
/// <summary>
/// The key for Column (read-only property)
/// </summary>
internal static readonly DependencyPropertyKey ColumnPropertyKey =
DependencyProperty.RegisterReadOnly(
"Column",
typeof(GridViewColumn),
typeof(GridViewColumnHeader),
null);
/// <summary>
/// The DependencyProperty for the Column property.
/// </summary>
public static readonly DependencyProperty ColumnProperty =
ColumnPropertyKey.DependencyProperty;
/// <summary>
/// Column associated with this header
/// </summary>
public GridViewColumn Column
{
get { return (GridViewColumn)GetValue(ColumnProperty); }
}
/// <summary>
/// The key for Role (read-only property)
/// </summary>
internal static readonly DependencyPropertyKey RolePropertyKey =
DependencyProperty.RegisterReadOnly(
"Role",
typeof(GridViewColumnHeaderRole),
typeof(GridViewColumnHeader),
new FrameworkPropertyMetadata(GridViewColumnHeaderRole.Normal));
/// <summary>
/// The DependencyProperty for the Role property.
/// </summary>
public static readonly DependencyProperty RoleProperty =
RolePropertyKey.DependencyProperty;
/// <summary>
/// What the role of the header is: Normal, Floating, Padding.
/// </summary>
[Category("Behavior")]
public GridViewColumnHeaderRole Role
{
get { return (GridViewColumnHeaderRole)GetValue(RoleProperty); }
}
#endregion Public Properties
#if OLD_AUTOMATION
//-------------------------------------------------------------------
//
// IInvodeProvider
//
//-------------------------------------------------------------------
void IInvokeProvider.Invoke()
{
IsAccessKeyOrAutomation = true;
OnClick();
}
#endif
//-------------------------------------------------------------------
//
// Protected Methods
//
//-------------------------------------------------------------------
#region Protected Methods
/// <summary>
/// This is the method that responds to the MouseButtonEvent event.
/// </summary>
/// <param name="e">Event arguments</param>
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonUp(e);
// give parent a chance to handle MouseButtonEvent (for GridViewHeaderRowPresenter by default)
e.Handled = false;
if (ClickMode == ClickMode.Hover && IsMouseCaptured)
{
ReleaseMouseCapture();
}
}
/// <summary>
/// This is the method that responds to the MouseButtonEvent event.
/// </summary>
/// <param name="e">Event arguments</param>
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
// give parent a chance to handle MouseButtonEvent (for GridViewHeaderRowPresenter by default)
e.Handled = false;
//If ClickMode is Hover, we must capture mouse in order to let column reorder work correctly (Bug#1496673)
if (ClickMode == ClickMode.Hover && e.ButtonState == MouseButtonState.Pressed)
{
CaptureMouse();
}
}
/// <summary>
/// This is the method that responds to the MouseMoveEvent event.
/// </summary>
/// <param name="e">Event arguments</param>
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
// Override base method: if left mouse is pressed, always set IsPressed as true
if ((ClickMode != ClickMode.Hover) &&
(IsMouseCaptured && (Mouse.PrimaryDevice.LeftButton == MouseButtonState.Pressed)))
{
SetValue(ButtonBase.IsPressedPropertyKey, BooleanBoxes.TrueBox);
}
e.Handled = false;
}
/// <summary>
/// Override for <seealso cref="UIElement.OnRenderSizeChanged"/>
/// </summary>
protected internal override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
base.OnRenderSizeChanged(sizeInfo);
// when render size is changed, check to hide the previous header's right half gripper
CheckWidthForPreviousHeaderGripper();
}
/// <summary>
/// Override base method: raises the Click event only when not re-ordering
/// </summary>
protected override void OnClick()
{
// if not suppress click event
if (!SuppressClickEvent)
{
// if is clicked by access key or automation,
// otherwise should be clicked by mouse
if (IsAccessKeyOrAutomation || !IsMouseOutside())
{
IsAccessKeyOrAutomation = false;
ClickImplement();
MakeParentGotFocus();
}
}
}
/// <summary>
/// The Access key for this control was invoked.
/// </summary>
protected override void OnAccessKey(AccessKeyEventArgs e)
{
IsAccessKeyOrAutomation = true;
base.OnAccessKey(e);
}
/// <summary>
/// Stop Style, ContentTemplate , ContentTemplateSelector, ContextMenu and ToolTip properties
/// from been serialized in case the value are pushed from GridView or GridViewHeaderRowPresenter.
/// </summary>
protected internal override bool ShouldSerializeProperty(DependencyProperty dp)
{
if (IsInternalGenerated)
{
// we should never reach here since this header is instantiated by HeaderRowPresenter.
Debug.Assert(false, "Method ShouldSerializeProperty is called on an internally generated GridViewColumnHeader.");
// nothing should be serialized from this object.
return false;
}
Flags flag, ignoreFlag;
PropertyToFlags(dp, out flag, out ignoreFlag); //ignoreFlag is never used in this method.
return ((flag == Flags.None) || GetFlag(flag))
&& base.ShouldSerializeProperty(dp);
}
/// <summary>
/// An event reporting the mouse entered this element.
/// </summary>
/// <param name="e">Event arguments</param>
//Override OnMouseEnter/Leave to process the ClickMode == Hover case
protected override void OnMouseEnter(MouseEventArgs e)
{
if (HandleIsMouseOverChanged())
{
e.Handled = true;
}
}
/// <summary>
/// An event reporting the mouse left this element.
/// </summary>
/// <param name="e">Event arguments</param>
protected override void OnMouseLeave(MouseEventArgs e)
{
if (HandleIsMouseOverChanged())
{
e.Handled = true;
}
}
/// <summary>
/// An event announcing that the keyboard is no longer focused
/// </summary>
/// <param name="e">Event arguments</param>
protected override void OnLostKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
base.OnLostKeyboardFocus(e);
if (ClickMode == ClickMode.Hover && IsMouseCaptured)
{
ReleaseMouseCapture();
}
}
#endregion Protected Methods
//-------------------------------------------------------------------
//
// Internal Methods
//
//-------------------------------------------------------------------
#region Internal Methods
/// <summary>
/// This method is called when column header is clicked via IInvokeProvider.
/// </summary>
internal void AutomationClick()
{
IsAccessKeyOrAutomation = true;
OnClick();
}
// cancel resizing if Escape key down.
internal void OnColumnHeaderKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Escape && _headerGripper != null && _headerGripper.IsDragging)
{
// NOTE: this will cause Thumb to complete the dragging and fire drag
// complete event with the Canceled property as 'True'. Handler
// OnColumnHeaderGripperDragCompleted will restore the width.
_headerGripper.CancelDrag();
e.Handled = true;
}
}
// Check to see if hide previous header's right half gripper
internal void CheckWidthForPreviousHeaderGripper()
{
bool hideGripperRightHalf = false;
if (_headerGripper != null)
{
// when header's width is less than gripper's width,
// hide the right half of the left header's gripper
hideGripperRightHalf = DoubleUtil.LessThan(ActualWidth, _headerGripper.Width);
}
if (_previousHeader != null)
{
_previousHeader.HideGripperRightHalf(hideGripperRightHalf);
}
UpdateGripperCursor();
}
// Fix for bug 1269757 in Windows OS Bugs. Reset the background visual brush ref to
// avoid keeping it alive. Keeping a VisualBrush alive causes us to assume that the
// entire Visual tree is a graph, preventing an optimized render walk of only
// the dirty subtree. We would end up rendering all of our realizations on each
// frame, causing high CPU consumption when a large realization tree is present.
internal void ResetFloatingHeaderCanvasBackground()
{
if (_floatingHeaderCanvas != null)
{
_floatingHeaderCanvas.Background = null;
}
}
/// <summary>
/// This method is called iff related properties are passed from GirdView/GridViewColumn to header.
/// And must use this method to update property from GirdView/GridViewColumn to header.
///
/// If this header is instantiated by user, before actually update the property,
/// this method will turn on the IgnoreXXX flag. And the PropertyChangeCallBack
/// will check this flag, and know that this update is an internal operation. By
/// doing this, we can distinguish {the property change by user} from {the change
/// by HeaderRowPresenter}.
/// </summary>
/// <param name="dp">the property you want to update</param>
/// <param name="value">a null value will result in ClearValue operation</param>
internal void UpdateProperty(DependencyProperty dp, object value)
{
Flags ignoreFlag = Flags.None;
if (!IsInternalGenerated)
{
Flags flag;
PropertyToFlags(dp, out flag, out ignoreFlag);
Debug.Assert(flag != Flags.None && ignoreFlag != Flags.None, "Invalid parameter dp.");
if (GetFlag(flag)) /* user has provided value for the property */
{
return;
}
else
{
SetFlag(ignoreFlag, true);
}
}
if (value != null)
{
SetValue(dp, value);
}
else
{
ClearValue(dp);
}
SetFlag(ignoreFlag, false);
}
#endregion Internal Methods
//-------------------------------------------------------------------
//
// Internal Properties
//
//-------------------------------------------------------------------
#region Internal Properties
#region DTypeThemeStyleKey
// Returns the DependencyObjectType for the registered ThemeStyleKey's default
// value. Controls will override this method to return approriate types.
internal override DependencyObjectType DTypeThemeStyleKey
{
get { return _dType; }
}
private static DependencyObjectType _dType;
#endregion DTypeThemeStyleKey
#region PreviousVisualHeader
// Link to the previous visual column header, the value is filled by GridViewHeaderRowPresenter
internal GridViewColumnHeader PreviousVisualHeader
{
get { return _previousHeader; }
set { _previousHeader = value; }
}
private GridViewColumnHeader _previousHeader;
#endregion PreviousVisualHeader
#region SuppressClickEvent
// indicating whether to fire click event
internal bool SuppressClickEvent
{
get { return GetFlag(Flags.SuppressClickEvent); }
set { SetFlag(Flags.SuppressClickEvent, value); }
}
#endregion SuppressClickEvent
// the source header for floating
// This property is only used to create VisualBrush for floating header,
// and will be set to null when VisualBrush is created. Set to null for GC.
internal GridViewColumnHeader FloatSourceHeader
{
get { return _srcHeader; }
set { _srcHeader = value; }
}
// whether this header is generated by GVHeaderRowPresenter or user
internal bool IsInternalGenerated
{
get { return GetFlag(Flags.IsInternalGenerated); }
set { SetFlag(Flags.IsInternalGenerated, value); }
}
#endregion Internal Properties
//-------------------------------------------------------------------
//
// Accessibility
//
//-------------------------------------------------------------------
#region Accessibility
/// <summary>
/// Creates AutomationPeer (<see cref="UIElement.OnCreateAutomationPeer"/>)
/// </summary>
protected override AutomationPeer OnCreateAutomationPeer()
{
return new GridViewColumnHeaderAutomationPeer(this);
}
#endregion
//-------------------------------------------------------------------
//
// Private Methods
//
//-------------------------------------------------------------------
#region Private Methods
private static void PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
GridViewColumnHeader header = (GridViewColumnHeader)d;
if (!header.IsInternalGenerated)
{
Flags flag, ignoreFlag;
PropertyToFlags(e.Property, out flag, out ignoreFlag);
if (!header.GetFlag(ignoreFlag)) // value is updated by user
{
if (e.NewValueSource == BaseValueSourceInternal.Local)
{
header.SetFlag(flag, true);
}
else
{
header.SetFlag(flag, false);
GridViewHeaderRowPresenter headerRowPresenter = header.Parent as GridViewHeaderRowPresenter;
if (headerRowPresenter != null)
{
headerRowPresenter.UpdateHeaderProperty(header, e.Property);
}
}
}
}
}
private static void PropertyToFlags(DependencyProperty dp, out Flags flag, out Flags ignoreFlag)
{
if (dp == GridViewColumnHeader.StyleProperty)
{
flag = Flags.StyleSetByUser;
ignoreFlag = Flags.IgnoreStyle;
}
else if (dp == GridViewColumnHeader.ContentTemplateProperty)
{
flag = Flags.ContentTemplateSetByUser;
ignoreFlag = Flags.IgnoreContentTemplate;
}
else if (dp == GridViewColumnHeader.ContentTemplateSelectorProperty)
{
flag = Flags.ContentTemplateSelectorSetByUser;
ignoreFlag = Flags.IgnoreContentTemplateSelector;
}
else if (dp == GridViewColumnHeader.ContentStringFormatProperty)
{
flag = Flags.ContentStringFormatSetByUser;
ignoreFlag = Flags.IgnoreContentStringFormat;
}
else if (dp == GridViewColumnHeader.ContextMenuProperty)
{
flag = Flags.ContextMenuSetByUser;
ignoreFlag = Flags.IgnoreContextMenu;
}
else if (dp == GridViewColumnHeader.ToolTipProperty)
{
flag = Flags.ToolTipSetByUser;
ignoreFlag = Flags.IgnoreToolTip;
}
else
{
flag = ignoreFlag = Flags.None;
}
}
/// <summary>
/// Hide the right half of gripper
/// +-----------------+
/// + +----+
/// + Header + Re +
/// + + +
/// + +----+
/// +-----------------+
/// </summary>
/// <param name="hide"></param>
private void HideGripperRightHalf(bool hide)
{
if (_headerGripper != null)
{
// hide gripper's right half by setting Parent.ClipToBounds=true
FrameworkElement gripperContainer = _headerGripper.Parent as FrameworkElement;
if (gripperContainer != null)
{
gripperContainer.ClipToBounds = hide;
}
}
}
// Save the original width before header resize
private void OnColumnHeaderGripperDragStarted(object sender, DragStartedEventArgs e)
{
MakeParentGotFocus();
_originalWidth = ColumnActualWidth;
e.Handled = true;
}
//Because ColumnHeader isn't focusable, we must forward focus to ListView when user invoke the header by access key
private void MakeParentGotFocus()
{
GridViewHeaderRowPresenter headerRP = this.Parent as GridViewHeaderRowPresenter;
if (headerRP != null)
{
headerRP.MakeParentItemsControlGotFocus();
}
}
// Resize the header
private void OnColumnHeaderResize(object sender, DragDeltaEventArgs e)
{
double width = ColumnActualWidth + e.HorizontalChange;
if (DoubleUtil.LessThanOrClose(width, 0.0))
{
width = 0.0;
}
UpdateColumnHeaderWidth(width);
e.Handled = true;
}
private void OnColumnHeaderGripperDragCompleted(object sender, DragCompletedEventArgs e)
{
if (e.Canceled)
{
// restore to original width
UpdateColumnHeaderWidth(_originalWidth);
}
UpdateGripperCursor();
e.Handled = true;
}
/// <summary>
/// Find gripper and register drag event
///
/// The default style for GridViewColumnHeader is
/// +-----------------+
/// + +----------+
/// + Header + Gripper +
/// + + +
/// + +----------+
/// +-----------------+
/// </summary>
private void HookupGripperEvents()
{
UnhookGripperEvents();
_headerGripper = GetTemplateChild(HeaderGripperTemplateName) as Thumb;
if (_headerGripper != null)
{
_headerGripper.DragStarted += new DragStartedEventHandler(OnColumnHeaderGripperDragStarted);
_headerGripper.DragDelta += new DragDeltaEventHandler(OnColumnHeaderResize);
_headerGripper.DragCompleted += new DragCompletedEventHandler(OnColumnHeaderGripperDragCompleted);
_headerGripper.MouseDoubleClick += new MouseButtonEventHandler(OnGripperDoubleClicked);
_headerGripper.MouseEnter += new MouseEventHandler(OnGripperMouseEnterLeave);
_headerGripper.MouseLeave += new MouseEventHandler(OnGripperMouseEnterLeave);
_headerGripper.Cursor = SplitCursor;
}
}
private void OnGripperDoubleClicked(object sender, MouseButtonEventArgs e)
{
if (Column != null)
{
if (Double.IsNaN(Column.Width))
{
// force update will be triggered
Column.Width = Column.ActualWidth;
}
Column.Width = Double.NaN;
e.Handled = true;
}
}
/// <summary>
/// Clear gripper event
/// </summary>
private void UnhookGripperEvents()
{
if (_headerGripper != null)
{
_headerGripper.DragStarted -= new DragStartedEventHandler(OnColumnHeaderGripperDragStarted);
_headerGripper.DragDelta -= new DragDeltaEventHandler(OnColumnHeaderResize);
_headerGripper.DragCompleted -= new DragCompletedEventHandler(OnColumnHeaderGripperDragCompleted);
_headerGripper.MouseDoubleClick -= new MouseButtonEventHandler(OnGripperDoubleClicked);
_headerGripper.MouseEnter -= new MouseEventHandler(OnGripperMouseEnterLeave);
_headerGripper.MouseLeave -= new MouseEventHandler(OnGripperMouseEnterLeave);
_headerGripper = null;
}
}
/// <SecurityNote>
/// Critical - Asserts permissions required to call Cursor constructor in partial trust
/// TreatAsSafe - Can only be used to create one of two specific cursors (which are embedded resources within assembly).
/// The following Permissions are required to invoke Cursor.LoadFromStream method which writes stream to a temporary file and loads the Cursor from that file.
/// The Environment permission is safe because even if the caller sets the %TEMP% variable to a critical location
/// before executing the method, the caller does not choose the filename that is written to.
/// Additionally the temp filename algorithm tries to avoid name collisions.
/// Therefore it is reasonably unlikely that the caller can use this method to overwrite a critical file.
/// The FileIO write permission is safe because from the above justification the file being written to is reasonably safe.
/// The Unmanaged code permission is safe because it is used for a safe p-invoke to load a cursor from a file
/// (the bytes read are never exposed to the caller)
/// </SecurityNote>
[SecurityCritical, SecurityTreatAsSafe]
private Cursor GetCursor(int cursorID)
{
Invariant.Assert(cursorID == c_SPLIT || cursorID == c_SPLITOPEN, "incorrect cursor type");
Cursor cursor = null;
System.IO.Stream stream = null;
System.Reflection.Assembly assembly = this.GetType().Assembly;
if (cursorID == c_SPLIT)
{
stream = assembly.GetManifestResourceStream("split.cur");
}
else if (cursorID == c_SPLITOPEN)
{
stream = assembly.GetManifestResourceStream("splitopen.cur");
}
Debug.Assert(stream != null, "stream is null");
if (stream != null)
{
PermissionSet permissions = new PermissionSet(null);
FileIOPermission filePermission = new FileIOPermission(PermissionState.None);
filePermission.AllLocalFiles = FileIOPermissionAccess.Write;
permissions.AddPermission(filePermission);
permissions.AddPermission(new EnvironmentPermission(PermissionState.Unrestricted));
permissions.AddPermission(new SecurityPermission(SecurityPermissionFlag.UnmanagedCode));
permissions.Assert();
try
{
cursor = new Cursor(stream);
}
finally
{
CodeAccessPermission.RevertAssert();
}
}
return cursor;
}
private void UpdateGripperCursor()
{
if (_headerGripper != null && !_headerGripper.IsDragging)
{
Cursor gripperCursor;
if (DoubleUtil.IsZero(ActualWidth))
{
gripperCursor = SplitOpenCursor;
}
else
{
gripperCursor = SplitCursor;
}
Debug.Assert(gripperCursor != null, "gripper cursor is null");
if (gripperCursor != null)
{
_headerGripper.Cursor = gripperCursor;
}
}
}
// Set column header width and associated column width
private void UpdateColumnHeaderWidth(double width)
{
if (Column != null)
{
Column.Width = width;
}
else
{
Width = width;
}
}
private bool IsMouseOutside()
{
Point pos = Mouse.PrimaryDevice.GetPosition(this);
return !((pos.X >= 0) && (pos.X <= ActualWidth) && (pos.Y >= 0) && (pos.Y <= ActualHeight));
}
private void ClickImplement()
{
if (AutomationPeer.ListenerExists(AutomationEvents.InvokePatternOnInvoked))
{
AutomationPeer peer = UIElementAutomationPeer.CreatePeerForElement(this);
if (peer != null)
peer.RaiseAutomationEvent(AutomationEvents.InvokePatternOnInvoked);
}
base.OnClick();
}
private bool GetFlag(Flags flag)
{
return (_flags & flag) == flag;
}
private void SetFlag(Flags flag, bool set)
{
if (set)
{
_flags |= flag;
}
else
{
_flags &= (~flag);
}
}
// update the background visual brush
private void UpdateFloatingHeaderCanvas()
{
if (_floatingHeaderCanvas != null
&& FloatSourceHeader != null)
{
// because the gripper is partially positioned out of the header, we need to
// map the appropriate area(viewbox) in the source header to visual brush
// to avoid a distorded image on the floating header.
Vector offsetVector = VisualTreeHelper.GetOffset(FloatSourceHeader);
VisualBrush visualBrush = new VisualBrush(FloatSourceHeader);
// set visual brush's mapping
visualBrush.ViewboxUnits = BrushMappingMode.Absolute;
visualBrush.Viewbox = new Rect(offsetVector.X, offsetVector.Y, FloatSourceHeader.ActualWidth, FloatSourceHeader.ActualHeight);
_floatingHeaderCanvas.Background = visualBrush;
FloatSourceHeader = null;
}
}
/// <summary>
/// Handle IsMouseOverChanged when ClickMode is Hover
/// </summary>
// Note: When ClickMode is Hover, ColumnHeader will be click when mouse is over it
// Here are 2 cases:
// 1) Mouse is over column header
// OnClick will be called
// 2) Mouse is over gripper
// OnClick won't be called, only when the mouse leaves the gripper and move to header, OnClick will be called.
private bool HandleIsMouseOverChanged()
{
if (ClickMode == ClickMode.Hover)
{
if (IsMouseOver &&
//1) Gripper doesn't exist; 2) Gripper exists and Mouse isn't on Gripper;
(_headerGripper == null || !_headerGripper.IsMouseOver))
{
// Hovering over the button will click in the OnHover click mode
SetValue(IsPressedPropertyKey, BooleanBoxes.Box(true));
OnClick();
}
else
{
ClearValue(IsPressedPropertyKey);
}
return true;
}
return false;
}
// When mouse enters/leaves gripper, recall HandleIsMouseOverChanged to verify is mouse over header or not
private void OnGripperMouseEnterLeave(object sender, MouseEventArgs e)
{
HandleIsMouseOverChanged();
}
#endregion Private Methods
//-------------------------------------------------------------------
//
// Private Properties
//
//-------------------------------------------------------------------
#region Private Properties
#region SplitCursor
private Cursor SplitCursor
{
get
{
if (_splitCursorCache == null)
{
_splitCursorCache = GetCursor(c_SPLIT);
}
return _splitCursorCache;
}
}
static private Cursor _splitCursorCache = null;
#endregion SplitCursor
#region SplitOpenCursor
private Cursor SplitOpenCursor
{
get
{
if (_splitOpenCursorCache == null)
{
_splitOpenCursorCache = GetCursor(c_SPLITOPEN);
}
return _splitOpenCursorCache;
}
}
static private Cursor _splitOpenCursorCache = null;
#endregion SplitOpenCursor
// is clicked by access key or automation
private bool IsAccessKeyOrAutomation
{
get { return GetFlag(Flags.IsAccessKeyOrAutomation); }
set { SetFlag(Flags.IsAccessKeyOrAutomation, value); }
}
private double ColumnActualWidth
{
get { return (Column != null ? Column.ActualWidth : ActualWidth); }
}
#endregion Private Properties
//-------------------------------------------------------------------
//
// Private Fields
//
//-------------------------------------------------------------------
#region Private Fields
/// <summary>
/// StyleSetByUser: the value of Style property is set by user.
/// IgnoreStyle: the OnStyleChanged is triggered by HeaderRowPresenter,
/// not by user. Don't turn on the StyleSetByUser flag.
/// And so on
/// (Only for user provided header. Ignored for internal generated header)
///
/// Go to UpdateProperty and OnPropetyChanged for how these flags work.
/// </summary>
[Flags]
private enum Flags
{
// IgnoreXXX can't be combined into one flag.
// Reason:
// Define a Style with ContentTemplate and assign it to GridViewColumn.HeaderContainerStyle property. GridViewColumnHeader.OnPropertyChagned method will be called twice.
// The first call is for ContentTemplate property. In this call, IgnoreContentTemplate is false.
// The second call is for Style property. In this call, IgnoreStyle is true.
// One flag can’t distinguish them.
None = 0,
StyleSetByUser = 0x00000001,
IgnoreStyle = 0x00000002,
ContentTemplateSetByUser = 0x00000004,
IgnoreContentTemplate = 0x00000008,
ContentTemplateSelectorSetByUser = 0x00000010,
IgnoreContentTemplateSelector = 0x00000020,
ContextMenuSetByUser = 0x00000040,
IgnoreContextMenu = 0x00000080,
ToolTipSetByUser = 0x00000100,
IgnoreToolTip = 0x00000200,
SuppressClickEvent = 0x00000400,
IsInternalGenerated = 0x00000800,
IsAccessKeyOrAutomation = 0x00001000,
ContentStringFormatSetByUser = 0x00002000,
IgnoreContentStringFormat = 0x00004000,
}
private Flags _flags;
private Thumb _headerGripper;
private double _originalWidth;
// canvas for floating header
private Canvas _floatingHeaderCanvas;
private GridViewColumnHeader _srcHeader;
// cursor id in embedded win32 resource
private const int c_SPLIT = 100;
private const int c_SPLITOPEN = 101;
// Part name used in the style. The class TemplatePartAttribute should use the same name
private const string HeaderGripperTemplateName = "PART_HeaderGripper";
private const string FloatingHeaderCanvasTemplateName = "PART_FloatingHeaderCanvas";
#endregion Private Fields
}
}
|