Reputation: 1189
I have a custom adorner that is added to controls when they are selected. The issue is, I also have a custom global event registered to automatically select all text in a textbox when it is focused.
The issue I have is that my adorner does not work when I try to use it on a textbox with selected text. For some reason it tries to drag the selected text instead. The weird thing is I can drag the selected text and then after that drop is completed I can then use my adorner. I am not sure why or how to fix it.
I have tried cancelling the drag command on the individual textboxes but that also prevents the adorner from working.
Is there a way I can give the adorner a higher priority over the selected text?
Here is my custom adorner
public class DraggingAdorner : Adorner
{
Thumb draggingIcon = null;
Thumb resizeThumb = null;
Border border = null;
VisualCollection visualChildren;
public DraggingAdorner(UIElement adornedElement)
: base(adornedElement)
{
visualChildren = new VisualCollection(this);
BuildBorder();
BuildDraggingIcon();
BuildResizer();
draggingIcon.DragDelta += DraggingIcon_DragDelta;
resizeThumb.DragDelta += Resize;
resizeThumb.MouseDoubleClick += ResizeThumb_MouseDoubleClick;
}
private void ResizeThumb_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
FrameworkElement adornedElement = this.AdornedElement as FrameworkElement;
Thumb hitThumb = sender as Thumb;
if (adornedElement == null || hitThumb == null) return;
FrameworkElement parentElement = adornedElement.Parent as FrameworkElement;
var tb = adornedElement as TextBox;
if (tb == null) return;
tb.Height = Double.NaN;
tb.Width = Double.NaN;
e.Handled = true;
}
private void BuildBorder()
{
if (border != null) return;
border = new Border();
border.BorderBrush = new SolidColorBrush(Colors.Black);
border.BorderThickness = new Thickness(1);
border.Opacity = .5;
visualChildren.Add(border);
}
private void BuildDraggingIcon()
{
if (draggingIcon != null) return;
draggingIcon = new Thumb();
draggingIcon.Cursor = Cursors.Hand;
draggingIcon.Height = draggingIcon.Width = 12;
draggingIcon.Opacity = 0.40;
draggingIcon.Background = new SolidColorBrush(Colors.Black);
visualChildren.Add(draggingIcon);
}
private void BuildResizer()
{
if (resizeThumb != null) return;
resizeThumb = new Thumb();
// Set some arbitrary visual characteristics.
resizeThumb.Cursor = Cursors.ScrollAll;
resizeThumb.Height = resizeThumb.Width = 10;
resizeThumb.Opacity = 0.40;
resizeThumb.Background = new SolidColorBrush(Colors.Red);
visualChildren.Add(resizeThumb);
}
private void DraggingIcon_DragDelta(object sender, DragDeltaEventArgs e)
{
FrameworkElement adornedElement = this.AdornedElement as FrameworkElement;
Thumb hitThumb = sender as Thumb;
if (adornedElement == null || hitThumb == null) return;
FrameworkElement parentElement = adornedElement.Parent as FrameworkElement;
var left = e.HorizontalChange + Canvas.GetLeft(adornedElement);
if (left <= 0)
{
left = 1;
}
else if (left + adornedElement.ActualWidth >= parentElement.ActualWidth)
{
left = parentElement.ActualWidth - adornedElement.ActualWidth - 1;
}
var top = e.VerticalChange + Canvas.GetTop(adornedElement);
if (top <= 0)
{
top = 1;
}
else if (top + adornedElement.ActualHeight >= parentElement.ActualHeight)
{
top = parentElement.ActualHeight - adornedElement.ActualHeight - 1;
}
Canvas.SetLeft(adornedElement, left);
Canvas.SetTop(adornedElement, top);
}
// Handler for resizing from the bottom-right.
private void Resize(object sender, DragDeltaEventArgs e)
{
FrameworkElement adornedElement = this.AdornedElement as FrameworkElement;
Thumb hitThumb = sender as Thumb;
if (adornedElement == null || hitThumb == null) return;
FrameworkElement parentElement = adornedElement.Parent as FrameworkElement;
// Ensure that the Width and Height are properly initialized after the resize.
EnforceSize(adornedElement);
var left = Canvas.GetLeft(adornedElement);
var top = Canvas.GetTop(adornedElement);
var newWidth = Math.Max(adornedElement.Width + e.HorizontalChange, hitThumb.DesiredSize.Width);
var newHeight = Math.Max(e.VerticalChange + adornedElement.Height, hitThumb.DesiredSize.Height);
if (left + newWidth >= parentElement.ActualWidth)
{
newWidth = parentElement.ActualWidth - left;
}
if (top + newHeight >= parentElement.ActualHeight)
{
newHeight = parentElement.ActualHeight - top;
}
adornedElement.Width = newWidth;
adornedElement.Height = newHeight;
}
private void EnforceSize(FrameworkElement adornedElement)
{
if (adornedElement.Width.Equals(Double.NaN))
adornedElement.Width = adornedElement.DesiredSize.Width;
if (adornedElement.Height.Equals(Double.NaN))
adornedElement.Height = adornedElement.DesiredSize.Height;
FrameworkElement parent = adornedElement.Parent as FrameworkElement;
if (parent != null)
{
adornedElement.MaxHeight = parent.ActualHeight;
adornedElement.MaxWidth = parent.ActualWidth;
}
}
protected override Size ArrangeOverride(Size finalSize)
{
double desiredWidth = AdornedElement.DesiredSize.Width;
double desiredHeight = AdornedElement.DesiredSize.Height;
double adornerWidth = this.DesiredSize.Width;
double adornerHeight = this.DesiredSize.Height;
draggingIcon.Arrange(new Rect(-adornerWidth / 2, -adornerHeight / 2, adornerWidth, adornerHeight));
resizeThumb.Arrange(new Rect(desiredWidth - adornerWidth / 2, desiredHeight - adornerHeight / 2, adornerWidth, adornerHeight));
border.Arrange(new Rect(0, 0, adornerWidth, adornerHeight));
return finalSize;
}
protected override int VisualChildrenCount { get { return visualChildren.Count; } }
protected override Visual GetVisualChild(int index) { return visualChildren[index]; }
}
Here is my code to apply the adorner
private void OnPreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
e.Handled = AdornSelectedElement(sender as UIElement);
}
private bool AdornSelectedElement(UIElement element)
{
if (element == null) return false;
RemoveSelectedElement();
if (Canvas.GetLeft(element) < 0)
Canvas.SetLeft(element, 1);
var aLayer = AdornerLayer.GetAdornerLayer(element);
if ((aLayer.GetAdorners(element)?.Length ?? 0) > 0) return false;
aLayer.Add(new DraggingAdorner(element));
selectedElement = element;
return true;
}
private void RemoveSelectedElement()
{
if (selectedElement == null) return;
var aLayer = AdornerLayer.GetAdornerLayer(selectedElement);
if ((aLayer.GetAdorners(selectedElement)?.Length ?? 0) < 1) return;
aLayer.Remove(aLayer.GetAdorners(selectedElement)[0]);
selectedElement = null;
}
Here is my global event handler
protected override void OnStartup(StartupEventArgs e)
{
EventManager.RegisterClassHandler(typeof(TextBox), TextBox.GotKeyboardFocusEvent, new RoutedEventHandler(OnTextBoxFocus));
EventManager.RegisterClassHandler(typeof(TextBox), TextBox.GotFocusEvent, new RoutedEventHandler(OnTextBoxFocus));
base.OnStartup(e);
}
private void OnTextBoxFocus(object sender, RoutedEventArgs e)
{
var tb = sender as System.Windows.Controls.TextBox;
if (tb == null) return;
if (!String.IsNullOrWhiteSpace(tb.Text))
tb.SelectAll();
}
Lastly, here is the code to cancel the internal drag on the selected text that did not work
DataObject.AddCopyingHandler(tb, (s, e) =>
{
if (e.IsDragDrop)
{
e.CancelCommand();
}
});
I will be more than happy to add any more code that may be necessary.
EDIT: I do not think it is just because of the selected text in the TextBox. I think there is just something weird about my adorner and the textbox, but I am not sure what. I have a custom Image control that works fine.
SECOND EDIT: It looks like I actually can't do anything at all (IE Close the main window, click on other controls, etc) after selecting the textbox. For some reason there is something weird happening where the selection must be handled improperly and it doesn't exit this weird drag text mode. I need to drag and drop the text and after that I can do whatever I want again.
Upvotes: 0
Views: 141
Reputation: 1189
I sort of figured out the issue. Turns out this method
private void OnPreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
e.Handled = AdornSelectedElement(sender as UIElement);
}
Was causing the issues with textboxes. I have a custom image control that requires e.Handled be set to true for this selection to even fire properly, but on TextBoxes this causes the wierd selected text drag issue. I changed the
return true;
at the end of my
AdornSelectedElement
routine to read
return element is ImageBox;
and that fixed it. If anyone knows why this is I can provide the custom control's Xaml and we can discuss why this might have happened.
Thanks!
Upvotes: 0