I am looking to allow a user to drag a custom control around a Grid control smoothly, and I am not sure exactly what I am missing. All controls have AllowDrop set to true.
On MouseMove I do the following in the control that is being dragged:
DataObject dataObj = new DataObject("PersistentObject",this);
DragDrop.DoDragDrop(this, dataObj, DragDropEffects.Move);
On the Grid's DragEnter, DragOver, and DragDrop events, I set the effects of the DragEventArg to all.
The feedback shows that it the new location on the grid is a valid drop target, but it never seems to move.
Is there a way to do this on a Grid, or I am trying to do this on the wrong control (I am using a grid because the designer started with one)?
Are there other events that I have to fix, and/or are my current ones broken?
Edit: It would also be appreciated if it showed the control being dragged as it was happening. I am not sure if that is supposed to happen with my current approach, but that is the goal.
You can easily do this without using the built-in drag-drop stuff.
Point mouseOffset = new Point();
userControl.PreviewMouseLeftButtonDown += (sender, e) =>
{
mouseOffset = Mouse.GetPosition(userControl);
userControl.CaptureMouse();
};
userControl.PreviewMouseMove += (sender, e) =>
{
if (userControl.IsMouseCaptured)
{
Point mouseDelta = Mouse.GetPosition(userControl);
mouseDelta.Offset(-mouseOffset.X, -mouseOffset.Y);
userControl.Margin = new Thickness(
userControl.Margin.Left + mouseDelta.X,
userControl.Margin.Top + mouseDelta.Y,
userControl.Margin.Right - mouseDelta.X,
userControl.Margin.Bottom - mouseDelta.Y);
}
};
userControl.PreviewMouseLeftButtonUp += (sender, e) =>
{
userControl.ReleaseMouseCapture();
};
So the demo code I wrote up for this looks something like the following:
<Window x:Class="StackOverflowScratchpad.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Height="350"
Width="525">
<Grid>
<!-- I am using a Button here just for simplicity. The code
will function the same no matter what control is used. -->
<Button x:Name="userControl" Width="75" Height="23" />
</Grid>
</Window>
public MainWindow()
{
InitializeComponent();
Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object obj, RoutedEventArgs args)
{
// Add the above C# here.
}
I would suggest using a Canvas control instead. Here is an example Canvas control that anything you put in it will allow the user to drag around as well as resize:
Not the original code is NOT mine, although I have done some modifications to it, this was something I found on the web. (would love to quote the source here but I've long since forgotten unfortunately.)
Hopefully this helps, or at least points you in the right direction for what your looking for.
SuperCanvas:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using Meld.Helpers;
namespace Meld.Controls
{
public class SuperCanvas : Canvas
{
private AdornerLayer aLayer;
private bool isDown;
private bool isDragging;
private double originalLeft;
private double originalTop;
private bool selected;
private UIElement selectedElement;
private Point startPoint;
private bool locked;
public SuperCanvas()
{
Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
MouseLeftButtonDown += WorkspaceWindow_MouseLeftButtonDown;
MouseLeftButtonUp += DragFinishedMouseHandler;
MouseMove += WorkspaceWindow_MouseMove;
MouseLeave += Window1_MouseLeave;
PreviewMouseLeftButtonDown += myCanvas_PreviewMouseLeftButtonDown;
PreviewMouseLeftButtonUp += DragFinishedMouseHandler;
}
// Handler for drag stopping on leaving the window
private void Window1_MouseLeave(object sender, MouseEventArgs e)
{
StopDragging();
e.Handled = true;
}
// Handler for drag stopping on user choice
private void DragFinishedMouseHandler(object sender, MouseButtonEventArgs e)
{
StopDragging();
e.Handled = true;
}
// Method for stopping dragging
private void StopDragging()
{
if (isDown)
{
isDown = false;
isDragging = false;
}
}
// Hanler for providing drag operation with selected element
private void WorkspaceWindow_MouseMove(object sender, MouseEventArgs e)
{
if (locked) return;
if (isDown)
{
if ((isDragging == false) &&
((Math.Abs(e.GetPosition(this).X - startPoint.X) >
SystemParameters.MinimumHorizontalDragDistance) ||
(Math.Abs(e.GetPosition(this).Y - startPoint.Y) > SystemParameters.MinimumVerticalDragDistance)))
isDragging = true;
if (isDragging)
{
Point position = Mouse.GetPosition(this);
SetTop(selectedElement, position.Y - (startPoint.Y - originalTop));
SetLeft(selectedElement, position.X - (startPoint.X - originalLeft));
}
}
}
// Handler for clearing element selection, adorner removal
private void WorkspaceWindow_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (locked) return;
if (selected)
{
selected = false;
if (selectedElement != null)
{
aLayer.Remove(aLayer.GetAdorners(selectedElement)[0]);
selectedElement = null;
}
}
}
// Handler for element selection on the canvas providing resizing adorner
private void myCanvas_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
//add code to lock dragging and dropping things.
if (locked)
{
e.Handled = true;
return;
}
// Remove selection on clicking anywhere the window
if (selected)
{
selected = false;
if (selectedElement != null)
{
// Remove the adorner from the selected element
aLayer.Remove(aLayer.GetAdorners(selectedElement)[0]);
selectedElement = null;
}
}
// If any element except canvas is clicked,
// assign the selected element and add the adorner
if (e.Source != this)
{
isDown = true;
startPoint = e.GetPosition(this);
selectedElement = e.Source as UIElement;
originalLeft = GetLeft(selectedElement);
originalTop = GetTop(selectedElement);
aLayer = AdornerLayer.GetAdornerLayer(selectedElement);
aLayer.Add(new ResizingAdorner(selectedElement));
selected = true;
e.Handled = true;
}
}
}
}
And the ResizingAdorner:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
namespace Meld.Helpers
{
internal class ResizingAdorner : Adorner
{
// Resizing adorner uses Thumbs for visual elements.
// The Thumbs have built-in mouse input handling.
Thumb topLeft, topRight, bottomLeft, bottomRight;
// To store and manage the adorner's visual children.
VisualCollection visualChildren;
// Initialize the ResizingAdorner.
public ResizingAdorner(UIElement adornedElement) : base(adornedElement)
{
visualChildren = new VisualCollection(this);
// Call a helper method to initialize the Thumbs
// with a customized cursors.
BuildAdornerCorner(ref topLeft, Cursors.SizeNWSE);
BuildAdornerCorner(ref topRight, Cursors.SizeNESW);
BuildAdornerCorner(ref bottomLeft, Cursors.SizeNESW);
BuildAdornerCorner(ref bottomRight, Cursors.SizeNWSE);
// Add handlers for resizing.
bottomLeft.DragDelta += new DragDeltaEventHandler(HandleBottomLeft);
bottomRight.DragDelta += new DragDeltaEventHandler(HandleBottomRight);
topLeft.DragDelta += new DragDeltaEventHandler(HandleTopLeft);
topRight.DragDelta += new DragDeltaEventHandler(HandleTopRight);
}
// Handler for resizing from the bottom-right.
void HandleBottomRight(object sender, DragDeltaEventArgs args)
{
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);
// Change the size by the amount the user drags the mouse, as long as it's larger
// than the width or height of an adorner, respectively.
adornedElement.Width = Math.Max(adornedElement.Width + args.HorizontalChange, hitThumb.DesiredSize.Width);
adornedElement.Height = Math.Max(args.VerticalChange + adornedElement.Height, hitThumb.DesiredSize.Height);
}
// Handler for resizing from the top-right.
void HandleTopRight(object sender, DragDeltaEventArgs args)
{
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);
// Change the size by the amount the user drags the mouse, as long as it's larger
// than the width or height of an adorner, respectively.
adornedElement.Width = Math.Max(adornedElement.Width + args.HorizontalChange, hitThumb.DesiredSize.Width);
//adornedElement.Height = Math.Max(adornedElement.Height - args.VerticalChange, hitThumb.DesiredSize.Height);
double height_old = adornedElement.Height;
double height_new = Math.Max(adornedElement.Height - args.VerticalChange, hitThumb.DesiredSize.Height);
double top_old = Canvas.GetTop(adornedElement);
adornedElement.Height = height_new;
Canvas.SetTop(adornedElement, top_old - (height_new - height_old));
}
// Handler for resizing from the top-left.
void HandleTopLeft(object sender, DragDeltaEventArgs args)
{
FrameworkElement adornedElement = AdornedElement as FrameworkElement;
Thumb hitThumb = sender as Thumb;
if (adornedElement == null || hitThumb == null) return;
// Ensure that the Width and Height are properly initialized after the resize.
EnforceSize(adornedElement);
// Change the size by the amount the user drags the mouse, as long as it's larger
// than the width or height of an adorner, respectively.
//adornedElement.Width = Math.Max(adornedElement.Width - args.HorizontalChange, hitThumb.DesiredSize.Width);
//adornedElement.Height = Math.Max(adornedElement.Height - args.VerticalChange, hitThumb.DesiredSize.Height);
double width_old = adornedElement.Width;
double width_new = Math.Max(adornedElement.Width - args.HorizontalChange, hitThumb.DesiredSize.Width);
double left_old = Canvas.GetLeft(adornedElement);
adornedElement.Width = width_new;
Canvas.SetLeft(adornedElement, left_old - (width_new - width_old));
double height_old = adornedElement.Height;
double height_new = Math.Max(adornedElement.Height - args.VerticalChange, hitThumb.DesiredSize.Height);
double top_old = Canvas.GetTop(adornedElement);
adornedElement.Height = height_new;
Canvas.SetTop(adornedElement, top_old - (height_new - height_old));
}
// Handler for resizing from the bottom-left.
void HandleBottomLeft(object sender, DragDeltaEventArgs args)
{
FrameworkElement adornedElement = AdornedElement as FrameworkElement;
Thumb hitThumb = sender as Thumb;
if (adornedElement == null || hitThumb == null) return;
// Ensure that the Width and Height are properly initialized after the resize.
EnforceSize(adornedElement);
// Change the size by the amount the user drags the mouse, as long as it's larger
// than the width or height of an adorner, respectively.
//adornedElement.Width = Math.Max(adornedElement.Width - args.HorizontalChange, hitThumb.DesiredSize.Width);
adornedElement.Height = Math.Max(args.VerticalChange + adornedElement.Height, hitThumb.DesiredSize.Height);
double width_old = adornedElement.Width;
double width_new = Math.Max(adornedElement.Width - args.HorizontalChange, hitThumb.DesiredSize.Width);
double left_old = Canvas.GetLeft(adornedElement);
adornedElement.Width = width_new;
Canvas.SetLeft(adornedElement, left_old - (width_new - width_old));
}
// Arrange the Adorners.
protected override Size ArrangeOverride(Size finalSize)
{
// desiredWidth and desiredHeight are the width and height of the element that's being adorned.
// These will be used to place the ResizingAdorner at the corners of the adorned element.
double desiredWidth = AdornedElement.DesiredSize.Width;
double desiredHeight = AdornedElement.DesiredSize.Height;
// adornerWidth & adornerHeight are used for placement as well.
double adornerWidth = this.DesiredSize.Width;
double adornerHeight = this.DesiredSize.Height;
topLeft.Arrange(new Rect(-adornerWidth / 2, -adornerHeight / 2, adornerWidth, adornerHeight));
topRight.Arrange(new Rect(desiredWidth - adornerWidth / 2, -adornerHeight / 2, adornerWidth, adornerHeight));
bottomLeft.Arrange(new Rect(-adornerWidth / 2, desiredHeight - adornerHeight / 2, adornerWidth, adornerHeight));
bottomRight.Arrange(new Rect(desiredWidth - adornerWidth / 2, desiredHeight - adornerHeight / 2, adornerWidth, adornerHeight));
// Return the final size.
return finalSize;
}
// Helper method to instantiate the corner Thumbs, set the Cursor property,
// set some appearance properties, and add the elements to the visual tree.
void BuildAdornerCorner(ref Thumb cornerThumb, Cursor customizedCursor)
{
if (cornerThumb != null) return;
cornerThumb = new Thumb();
// Set some arbitrary visual characteristics.
cornerThumb.Cursor = customizedCursor;
cornerThumb.Height = cornerThumb.Width = 5;
cornerThumb.Opacity = 0.40;
cornerThumb.Background = new SolidColorBrush(Colors.White);
visualChildren.Add(cornerThumb);
}
// This method ensures that the Widths and Heights are initialized. Sizing to content produces
// Width and Height values of Double.NaN. Because this Adorner explicitly resizes, the Width and Height
// need to be set first. It also sets the maximum size of the adorned element.
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;
}
}
// Override the VisualChildrenCount and GetVisualChild properties to interface with
// the adorner's visual collection.
protected override int VisualChildrenCount { get { return visualChildren.Count; } }
protected override Visual GetVisualChild(int index) { return visualChildren[index]; }
}
}
you have to handle the code own Drop event
Drop event Occurs when an object is dropped within the bounds of an element that is acting as a drop target.
private void grid_Drop(object sender, DragEventArgs eventarg)
{
// check data is present returns a bool
if (eventarg.Data.GetDataPresent("PersistentObject"))
{
var yourdata=eventarg.Data.GetData("PersistentObject")as Model(T) ;
if (yourdata!= null)
{
// add to gird or whatever.
// place the object as you desire
}
}
Some Drag&Drop Reference
I created a DragDropBehaviour class a few years back. You should be able to copy-paste it and just change the OnDrop function.
public static class DragDropBehaviour
{
#region CanDrag attached dependancy property
/// <summary>
/// The CanDrag attached property'str name.
/// </summary>
public const string CanDragPropertyName = "CanDrag";
/// <summary>
/// Gets the value of the CanDrag attached property
/// for a given dependency object.
/// </summary>
/// <param name="obj">The object for which the property value
/// is read.</param>
/// <returns>The value of the CanDrag property of the specified object.</returns>
public static bool GetCanDrag(DependencyObject obj)
{
return (bool)obj.GetValue(CanDragProperty);
}
/// <summary>
/// Sets the value of the CanDrag attached property
/// for a given dependency object.
/// </summary>
/// <param name="obj">The object to which the property value
/// is written.</param>
/// <param name="value">Sets the CanDrag value of the specified object.</param>
public static void SetCanDrag(DependencyObject obj, bool value)
{
obj.SetValue(CanDragProperty, value);
}
/// <summary>
/// Identifies the CanDrag attached property.
/// </summary>
public static readonly DependencyProperty CanDragProperty = DependencyProperty.RegisterAttached(
CanDragPropertyName,
typeof(bool),
typeof(DragDropBehaviour),
new UIPropertyMetadata(false, OnCanDragChanged));
#endregion
#region CanDrop attached dependancy property
/// <summary>
/// The CanDrop attached property'str name.
/// </summary>
public const string CanDropPropertyName = "CanDrop";
/// <summary>
/// Gets the value of the CanDrop attached property
/// for a given dependency object.
/// </summary>
/// <param name="obj">The object for which the property value
/// is read.</param>
/// <returns>The value of the CanDrop property of the specified object.</returns>
public static bool GetCanDrop(DependencyObject obj)
{
return (bool)obj.GetValue(CanDropProperty);
}
/// <summary>
/// Sets the value of the CanDrop attached property
/// for a given dependency object.
/// </summary>
/// <param name="obj">The object to which the property value
/// is written.</param>
/// <param name="value">Sets the CanDrop value of the specified object.</param>
public static void SetCanDrop(DependencyObject obj, bool value)
{
obj.SetValue(CanDropProperty, value);
}
/// <summary>
/// Identifies the CanDrop attached property.
/// </summary>
public static readonly DependencyProperty CanDropProperty = DependencyProperty.RegisterAttached(
CanDropPropertyName,
typeof(bool),
typeof(DragDropBehaviour),
new UIPropertyMetadata(false, OnCanDropChanged));
#endregion
#region DragDropAction attached dependancy property
/// <summary>
/// The DragDropAction attached property'str name.
/// </summary>
public const string DragDropActionPropertyName = "DragDropAction";
/// <summary>
/// Gets the value of the DragDropAction attached property
/// for a given dependency object.
/// </summary>
/// <param name="obj">The object for which the property value
/// is read.</param>
/// <returns>The value of the DragDropAction property of the specified object.</returns>
public static DragDropAction GetDragDropAction(DependencyObject obj)
{
return (DragDropAction)obj.GetValue(DragDropActionProperty);
}
/// <summary>
/// Sets the value of the DragDropAction attached property
/// for a given dependency object.
/// </summary>
/// <param name="obj">The object to which the property value
/// is written.</param>
/// <param name="value">Sets the DragDropAction value of the specified object.</param>
public static void SetDragDropAction(DependencyObject obj, DragDropAction value)
{
obj.SetValue(DragDropActionProperty, value);
}
/// <summary>
/// Identifies the DragDropAction attached property.
/// </summary>
public static readonly DependencyProperty DragDropActionProperty = DependencyProperty.RegisterAttached(
DragDropActionPropertyName,
typeof(DragDropAction),
typeof(DragDropBehaviour),
new UIPropertyMetadata(null));
#endregion
static void OnCanDragChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
FrameworkElement element = depObj as FrameworkElement;
if (element != null)
{
if ((bool)e.NewValue)
{
GetOrCreateAction(depObj).DragBehaviour(element, true);
}
else
{
GetOrCreateAction(depObj).DragBehaviour(element, false);
}
}
}
static void OnCanDropChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
FrameworkElement element = depObj as FrameworkElement;
if (element != null)
{
if ((bool)e.NewValue)
{
GetOrCreateAction(depObj).DropBehaviour(element, true);
}
else
{
GetOrCreateAction(depObj).DropBehaviour(element, false);
}
}
}
static DragDropAction GetOrCreateAction(DependencyObject depObj)
{
DragDropAction action = depObj.GetValue(DragDropActionProperty) as DragDropAction;
if (action == null)
{
action = new DragDropAction();
depObj.SetValue(DragDropActionProperty, action);
}
return action;
}
}
public class DragDropAction
{
Point _start;
FrameworkElement _dragged;
public void DragBehaviour(FrameworkElement element, bool enable)
{
if (enable)
{
element.PreviewMouseLeftButtonDown += OnPreviewMouseLeftButtonDown;
element.PreviewMouseMove += OnPreviewMouseMove;
}
else
{
element.PreviewMouseLeftButtonDown -= OnPreviewMouseLeftButtonDown;
element.PreviewMouseMove -= OnPreviewMouseMove;
}
}
public void DropBehaviour(FrameworkElement element, bool enable)
{
if (enable)
{
element.Drop += OnDrop;
element.AllowDrop = true;
}
else
{
element.Drop -= OnDrop;
element.AllowDrop = false;
}
}
void OnPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
FrameworkElement element = sender as FrameworkElement;
if (element != null)
{
int[] position = Win32Mouse.GetMousePosition();
_start = new Point(position[0], position[1]);
_dragged = element;
}
}
void OnPreviewMouseMove(object sender, MouseEventArgs e)
{
FrameworkElement element = sender as FrameworkElement;
if (element != null && _dragged != null)
{
int[] position = Win32Mouse.GetMousePosition();
Point currentPosition = new Point(position[0], position[1]);
Vector diff = _start - currentPosition;
if (e.LeftButton == MouseButtonState.Pressed &&
Math.Abs(diff.X) > (SystemParameters.MinimumHorizontalDragDistance) &&
Math.Abs(diff.Y) > (SystemParameters.MinimumVerticalDragDistance))
{
DragDropEffects effects = DragDrop.DoDragDrop(element, _dragged.DataContext, DragDropEffects.Move);
_dragged = null;
e.Handled = true;
}
}
}
void OnDrop(object sender, DragEventArgs e)
{
FrameworkElement element = sender as FrameworkElement;
if (element != null)
{
TabViewModel tab = element.DataContext as TabViewModel;
if (tab == null)
{
// TabViewModel not found it element, it's possible that the drop was done on the lastOpened 'Canvas' element.
var tabControls = element.FindVisualChildren<TabControl>();
var menus = element.FindVisualChildren<Menu>();
var itemControls = element.FindVisualChildren<ItemsControl>();
if (tabControls.Count > 0 && tabControls[0].Visibility == Visibility.Visible)
{
// If currently in 'horizontal mode' add to the active tab. If there is no active tab
// just add to the bottom tab.
tab = tabControls[0].SelectedItem as TabViewModel;
if (tab == null)
tab = tabControls[0].Items.GetItemAt(tabControls[0].Items.Count - 1) as TabViewModel;
}
else if (menus.Count > 0 && menus[0].Visibility == Visibility.Visible)
{
// If currently in 'vertical mode' add to the default tab, there is no 'active' menu item after all.
var tabs = menus[0].Items.SourceCollection as ObservableCollection<TabViewModel>;
tab = tabs.SingleOrDefault(obj => obj.Title == Configuration.DefaultTab) ?? tabs.LastOrDefault();
}
else if (itemControls.Count > 0 && itemControls[0].Visibility == Visibility.Visible)
{
var window = element.FindVisualParent<Window>();
if (window != null && window.DataContext is MainViewModel)
{
// Add the currently expanded tab.
MainViewModel mainViewModel = (MainViewModel)window.DataContext;
tab = mainViewModel.ExpandedTab;
// If no tab is expanded, add to the default tab or the bottom tab.
if (tab == null)
{
tab = mainViewModel.Tabs.SingleOrDefault(obj => obj.Title == Configuration.DefaultTab)
?? mainViewModel.Tabs.LastOrDefault();
}
}
}
}
if (tab != null)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
DispatcherHelper.UIDispatcher.BeginInvoke(new Action(() =>
{
string[] droppedFilePaths = e.Data.GetData(DataFormats.FileDrop, true) as string[];
foreach (string fileName in droppedFilePaths)
{
try
{
ApplicationModel model = ApplicationModel.FromFile(fileName, tab.Title);
ApplicationViewModel application = new ApplicationViewModel(model);
Messenger.Default.Send<ApplicationMessage>(ApplicationMessage.Add(application, tab));
}
catch (FileNotFoundException ex)
{
ServiceManager.GetService<IMessageBoxService>().Show(
"Could not add application - " + ex.Message, "Astounding Dock", MessageIcon.Error);
}
}
}));
e.Handled = true;
}
else if (e.Data.GetDataPresent<ApplicationViewModel>())
{
DispatcherHelper.UIDispatcher.BeginInvoke(new Action(() =>
{
ApplicationViewModel application = e.Data.GetData<ApplicationViewModel>();
Messenger.Default.Send<ApplicationMessage>(ApplicationMessage.Move(application, tab));
}));
e.Handled = true;
}
else
{
Debug.WriteLine("DragDropBehaviour: Unknown data droppped - " + String.Join(",", e.Data.GetFormats()));
}
}
}
}
}
https://github.com/notsonormal/AstoundingDock/blob/master/src/AstoundingDock/Ui/DragDropBehaviour.cs
You can enable dropping on a control like
<Canvas ui:DragDropBehaviour.CanDrop="True">
</Canvas>
And enable dragging and dropping like this
<Button ui:DragDropBehaviour.CanDrag="True" ui:DragDropBehaviour.CanDrop="True">
</Button>
https://github.com/notsonormal/AstoundingDock/blob/master/src/AstoundingDock/Views/MainWindow.xaml
It seems pretty generic but I only ever used it for one specific case so...
Related
I'm trying to implement a manual focus feature for my camera page so that the user can tap to focus the camera.
I'm following this StackOverflow question that's currently written in Java for native Android. I've been converting it to C# for my Xamarin.Forms Android app.
Here's what I have so far:
public class CameraPage : PageRenderer, TextureView.ISurfaceTextureListener, Android.Views.View.IOnTouchListener, IAutoFocusCallback
{
global::Android.Hardware.Camera camera;
TextureView textureView;
public void OnAutoFocus(bool success, Android.Hardware.Camera camera)
{
var parameters = camera.GetParameters();
if (parameters.FocusMode != Android.Hardware.Camera.Parameters.FocusModeContinuousPicture)
{
parameters.FocusMode = Android.Hardware.Camera.Parameters.FocusModeContinuousPicture;
if (parameters.MaxNumFocusAreas > 0)
{
parameters.FocusAreas = null;
}
camera.SetParameters(parameters);
camera.StartPreview();
}
}
public bool OnTouch(Android.Views.View v, MotionEvent e)
{
if (camera != null)
{
var parameters = camera.GetParameters();
camera.CancelAutoFocus();
Rect focusRect = CalculateTapArea(e.GetX(), e.GetY(), 1f);
if (parameters.FocusMode != Android.Hardware.Camera.Parameters.FocusModeAuto)
{
parameters.FocusMode = Android.Hardware.Camera.Parameters.FocusModeAuto;
}
if (parameters.MaxNumFocusAreas > 0)
{
List<Area> mylist = new List<Area>();
mylist.Add(new Android.Hardware.Camera.Area(focusRect, 1000));
parameters.FocusAreas = mylist;
}
try
{
camera.CancelAutoFocus();
camera.SetParameters(parameters);
camera.StartPreview();
camera.AutoFocus(OnAutoFocus); //Here is the issue. How do I use the callback?
}
catch (System.Exception ex)
{
Console.WriteLine(ex.ToString());
Console.Write(ex.StackTrace);
}
return true;
}
return false;
}
private Rect CalculateTapArea(object x, object y, float coefficient)
{
var focusAreaSize = 500;
int areaSize = Java.Lang.Float.ValueOf(focusAreaSize * coefficient).IntValue();
int left = clamp((int)x - areaSize / 2, 0, textureView.Width - areaSize);
int top = clamp((int)y - areaSize / 2, 0, textureView.Height - areaSize);
RectF rectF = new RectF(left, top, left + areaSize, top + areaSize);
Matrix.MapRect(rectF);
return new Rect((int)System.Math.Round(rectF.Left), (int)System.Math.Round(rectF.Top), (int)System.Math.Round(rectF.Right), (int)System.Math.Round(rectF.Bottom));
}
private int clamp(int x, int min, int max)
{
if (x > max)
{
return max;
}
if (x < min)
{
return min;
}
return x;
}
}
I've managed to convert most of it but I'm not sure how to properly use the AutoFocusCallback here. What should I do to call OnAutoFocus from my OnTouch event like in the java answer I linked above?
After I attached the callback, then all I need to do is subscribe the OnTouch event to my page correct or...?
For example, I tried:
textureView.Click += OnTouch; but 'no overload for 'OnTouch' matches delegate 'EventHandler'. Is there a specific event handler I need to use?
You can try change
camera.AutoFocus(OnAutoFocus);
to
camera.AutoFocus(this);
and it will be using OnAutoFocus because it implementation from IAutoFocusCallback.
And for your question about subscribe event you can try to subscribe event in OnElementChanged like this
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Page> e)
{
base.OnElementChanged(e);
if (e.OldElement != null || Element == null)
{
return;
}
try
{
this.SetOnTouchListener(this);
}
catch (Exception e)
{
}
}
And btw I don't see to use TextureView.ISurfaceTextureListener in this code.
All that happened in the linked Java answer is that they provided the code to run when the OS calls the callback:
camera.autoFocus(new Camera.AutoFocusCallback() {
#Override
public void onAutoFocus(boolean success, Camera camera) {
camera.cancelAutoFocus();
Parameters params = camera.getParameters();
if(params.getFocusMode() != Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE){
params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
camera.setParameters(params);
}
}
});
the above does not "call" the call back, just provides the call back code to run. the OS calls the call back. So in Xamarin, you need to pass in the type that is implementing the IAutoFocusCallback interface, so You should be able to do this I would think since CameraPage is implementing the IAutoFocusCallback interface:
camera.AutoFocus(this); // "this" refers to your current CameraPage which implements the interface.
the clue here is that when you type the opening parenthesis after camera.AutoFocus the popup shows that you need to pass in a type IAutoFocusCallback, which means any type that implements that interface, so in this case that is "this" CameraPage. :-)
Since there's no complete example here, here's mine.
This solution works fine, at least for me. The camera will focus continously until a focus point is tapped. It will then focus on the tap point until you move the camera away. Then it goes back to continous focus mode.
public class CameraPageRenderer : PageRenderer, TextureView.ISurfaceTextureListener, Android.Hardware.Camera.IPictureCallback, Android.Hardware.Camera.IShutterCallback, IAutoFocusCallback
{
// ... code removed for brevity
/// <summary>
/// Occurs whenever the user touches the screen. Here we set the focus mode to FocusModeAuto and set a focus area based on the tapped coordinates.
/// </summary>
public override bool OnTouchEvent(MotionEvent e)
{
var parameters = camera.GetParameters();
parameters.FocusMode = Camera.Parameters.FocusModeAuto;
if (parameters.MaxNumFocusAreas > 0)
{
var focusRect = CalculateTapArea(e.GetX(), e.GetY(), textureView.Width, textureView.Height, 50f);
parameters.FocusAreas = new List<Area>()
{
new Area(focusRect, 1000)
};
}
try
{
camera.CancelAutoFocus();
camera.SetParameters(parameters);
camera.AutoFocus(this);
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
return true;
}
/// <summary>
/// Auto focus callback. Here we reset the focus mode to FocusModeContinuousPicture and remove any focus areas
/// </summary>
public void OnAutoFocus(bool success, Camera camera)
{
var parameters = camera.GetParameters();
parameters.FocusMode = Parameters.FocusModeContinuousPicture;
if (parameters.MaxNumFocusAreas > 0)
{
parameters.FocusAreas = null;
}
camera.SetParameters(parameters);
}
/// <summary>
/// Calculates a tap area using the focus coordinates mentioned in <see href="https://developer.android.com/reference/android/hardware/Camera.Parameters.html#getFocusAreas()"/>
/// <para>
/// Coordinates of the rectangle range from -1000 to 1000. (-1000, -1000) is the upper left point. (1000, 1000) is the lower right point. The width and height of focus areas cannot be 0 or negative.</para>
/// </summary>
/// <param name="x">The X coordinate of the tapped area</param>
/// <param name="y">The Y coordinate of the tapped area</param>
/// <param name="width">The total width of the tappable area</param>
/// <param name="height">The total height of the tappable area</param>
/// <param name="focusAreaSize">The desired size (widht, height) of the created rectangle</param>
/// <returns></returns>
private Rect CalculateTapArea(float x, float y, float width, float height, float focusAreaSize)
{
var leftFloat = x * 2000 / width - 1000;
var topFloat = y * 2000 / height - 1000;
var left = RoundFocusCoordinate(leftFloat);
var top = RoundFocusCoordinate(topFloat);
var right = RoundFocusCoordinate(leftFloat + focusAreaSize);
var bottom = RoundFocusCoordinate(topFloat + focusAreaSize);
return new Rect(left, top, right, bottom);
}
/// <summary>
/// Round, convert to int, and clamp between -1000 and 1000
/// </summary>
private int RoundFocusCoordinate(float value)
{
var intValue = (int)Math.Round(value, 0, MidpointRounding.AwayFromZero);
return Math.Clamp(intValue, -1000, 1000);
}
// ... code removed for brevity
}
I want to make a window to slide up from the bottom of the screen when it opens. However im having an issue with my code, im getting the following error when adding my Method to the Loaded event
Error:
Additional information: Value cannot be null.
This is the code which adds the Method to the Eventhandler, and the Method:
//Position the Notification
var workingArea = SystemParameters.WorkArea;
this.Left = (workingArea.Width - this.ActualWidth) / 2;
//Create storyboard for animation
Loaded += animate;
}
}
}
public RoutedEventHandler animate(object sender, RoutedEventArgs e)
{
var workingArea = SystemParameters.WorkArea;
Storyboard sb = new Storyboard();
var slide = new DoubleAnimation()
{
BeginTime = TimeSpan.FromSeconds(2),
Duration = TimeSpan.FromSeconds(1),
By = -100
};
Storyboard.SetTarget(slide,this);
Storyboard.SetTargetName(this, "MyWindow");
Storyboard.SetTargetProperty(slide,new PropertyPath("(Window.Top)"));
this.Top = workingArea.Height - this.ActualHeight;
return null;
}
Edit:
This is the entire Window Code Behind, which should handle the animations and positioning.
/// <summary>
/// Interaction logic for NotificationAll.xaml
/// </summary>
public partial class NotificationAll : Window
{
public NotificationAll() : base()
{
InitializeComponent();
}
public new void Show()
{
//Ensure new notifications are placed above older ones
if (!Application.Current.Windows.Cast<Window>().Where(x =>
x != this).Any(x => x.GetType().Name == "NotificationAll"))
{
this.Topmost = true;
base.Show();
this.Owner = System.Windows.Application.Current.MainWindow;
//Position the Notification
var workingArea = SystemParameters.WorkArea;
this.Left = (workingArea.Width - this.ActualWidth) / 2;
//Create storyboard for animation
Loaded += SlideFromBottom;
}
}
public void SlideFromBottom(object sender, RoutedEventArgs e)
{
MessageBox.Show("h");
var workingArea = SystemParameters.WorkArea;
Storyboard sb = new Storyboard();
var slide = new DoubleAnimation()
{
BeginTime = TimeSpan.FromSeconds(2),
Duration = TimeSpan.FromSeconds(1),
By = -100
};
Storyboard.SetTarget(slide,this);
Storyboard.SetTargetName(this, "MyWindow");
Storyboard.SetTargetProperty(slide,new PropertyPath("(Window.Top)"));
this.Top = workingArea.Height - this.ActualHeight;
}
/// <summary>
/// Close window once animation is complete
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void DoubleAnimationCompleted(object sender, EventArgs e)
{
if (!this.IsMouseOver)
{
this.Close();
}
}
}
Well, it's pretty simple. You are calling your animate method and assigning the result of it to the Loaded event. The animate method always returns null in your case. If you want animate to be an event handler, you should not call it using the parenthesis. You should instead do Loaded += animate;
And it should have the correct signature: void animate(object sender, RoutedEventArgs e)
How to Pass and Set values to Usercontrol.
Here I have two question regarding a WPF project.
Overview:
In this project, I'm trying to use a Usercontrol as a resources on a canvas and connecting them with each other by LineGeometry. 'MyThumb' class i'm using to make control dragable and control set values.
where i'm using overrided 'OnApplyTemplate' method to pass values on the Usercontrol. To set value i'm using 'Template.FindName'. Here it is returning null.
Now the question is 'how to set values on usercontrol' in the code below:
Another question is related to line color. In the project i'm using LineGeometry to display lines between usercontrols but not able to change line colors.. want to set different lines with different color.
Here the question is how to change color of existing LineGeometry.
Please have a look to the codes i'm using:
Mythumb.cs
public class MyThumb : Thumb
{
#region Properties
public static readonly DependencyProperty TitleProperty = DependencyProperty.Register("Title", typeof(string), typeof(MyThumb), new UIPropertyMetadata(""));
public static readonly DependencyProperty ImageSourceProperty = DependencyProperty.Register("ImageSource", typeof(string), typeof(MyThumb), new UIPropertyMetadata(""));
// This property will hanlde the content of the textblock element taken from control template
public string Title
{
get { return (string)GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
// This property will handle the content of the image element taken from control template
public string ImageSource
{
get { return (string)GetValue(ImageSourceProperty); }
set { SetValue(ImageSourceProperty, value); }
}
public Point MyLocation
{
get
{
Point nm = new Point(Canvas.GetLeft(this), Canvas.GetTop(this));
return nm;
}
}
public List<LineGeometry> EndLines { get; private set; }
public List<LineGeometry> StartLines { get; private set; }
#endregion
#region Constructors
public MyThumb()
: base()
{
StartLines = new List<LineGeometry>();
EndLines = new List<LineGeometry>();
}
public MyThumb(string title, string imageSource, Point position)
: this()
{
//Setting ControlTemplate as Usercontrol 'ContactNode'
ControlTemplate templatex = new ControlTemplate();
var fec = new FrameworkElementFactory(typeof(ContactNode));
templatex.VisualTree = fec;
this.Template = templatex;
this.Title = (title != null) ? title : string.Empty;
this.ImageSource = (imageSource != null) ? imageSource : string.Empty;
this.SetPosition(position);
}
public MyThumb( string title, string imageSource, Point position, DragDeltaEventHandler dragDelta)
: this(title, imageSource, position)
{
this.DragDelta += dragDelta;
}
#endregion
// Helper method for setting the position of our thumb
public void SetPosition(Point value)
{
Canvas.SetLeft(this, value.X);
Canvas.SetTop(this, value.Y);
}
#region Linking logic
// This method establishes a link between current thumb and specified thumb.
// Returns a line geometry with updated positions to be processed outside.
public LineGeometry LinkTo(MyThumb target)
{
// Create new line geometry
LineGeometry line = new LineGeometry();
// Save as starting line for current thumb
this.StartLines.Add(line);
// Save as ending line for target thumb
target.EndLines.Add(line);
// Ensure both tumbs the latest layout
this.UpdateLayout();
target.UpdateLayout();
// Update line position
line.StartPoint = new Point(Canvas.GetLeft(this) + this.ActualWidth / 2, Canvas.GetTop(this) + this.ActualHeight / 2);
line.EndPoint = new Point(Canvas.GetLeft(target) + target.ActualWidth / 2, Canvas.GetTop(target) + target.ActualHeight / 2);
// return line for further processing
return line;
}
// This method establishes a link between current thumb and target thumb using a predefined line geometry
// Note: this is commonly to be used for drawing links with mouse when the line object is predefined outside this class
public bool LinkTo(MyThumb target, LineGeometry line)
{
// Save as starting line for current thumb
this.StartLines.Add(line);
// Save as ending line for target thumb
target.EndLines.Add(line);
// Ensure both tumbs the latest layout
this.UpdateLayout();
target.UpdateLayout();
// Update line position
line.StartPoint = new Point(Canvas.GetLeft(this) + this.ActualWidth / 2, Canvas.GetTop(this) + this.ActualHeight / 2);
line.EndPoint = new Point(Canvas.GetLeft(target) + target.ActualWidth / 2, Canvas.GetTop(target) + target.ActualHeight / 2);
return true;
}
#endregion
// This method updates all the starting and ending lines assigned for the given thumb
// according to the latest known thumb position on the canvas
public void UpdateLinks()
{
double left = Canvas.GetLeft(this);
double top = Canvas.GetTop(this);
for (int i = 0; i < this.StartLines.Count; i++)
this.StartLines[i].StartPoint = new Point(left + this.ActualWidth / 2, top + this.ActualHeight / 2);
for (int i = 0; i < this.EndLines.Count; i++)
this.EndLines[i].EndPoint = new Point(left + this.ActualWidth / 2, top + this.ActualHeight / 2);
}
// Upon applying template we apply the "Title" and "ImageSource" properties to the template elements.
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
// Access the textblock element of template and assign it if Title property defined
if (this.Title != string.Empty)
{
TextBlock txt = this.Template.FindName("tplTextBlock", this) as TextBlock;
if (txt != null) //getting null here unable to find element..
txt.Text = Title;
}
// Access the image element of our custom template and assign it if ImageSource property defined
if (this.ImageSource != string.Empty)
{
Image img = this.Template.FindName("tplImage", this) as Image;
if (img != null)
img.Source = new BitmapImage(new Uri(this.ImageSource, UriKind.Relative));
}
}
}
Window1.xaml.cs
public partial class Window1 : Window
{
// flag for enabling "New thumb" mode
bool isAddNewAction = false;
// flag for enabling "New link" mode
bool isAddNewLink = false;
// flag that indicates that the link drawing with a mouse started
bool isLinkStarted = false;
// variable to hold the thumb drawing started from
MyThumb linkedThumb;
// Line drawn by the mouse before connection established
LineGeometry link;
public Window1()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Point pp = new Point(myCanvas.ActualWidth/2,myCanvas.ActualHeight/2);
AdNewNode(pp, "ACTION", "/Images/gear_connection.png");
this.PreviewMouseLeftButtonDown += new MouseButtonEventHandler(Window1_PreviewMouseLeftButtonDown);
this.PreviewMouseMove += new MouseEventHandler(Window1_PreviewMouseMove);
this.PreviewMouseLeftButtonUp += new MouseButtonEventHandler(Window1_PreviewMouseLeftButtonUp);
}
// Event hanlder for dragging functionality support
private void onDragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
{
// Exit dragging operation during adding new link
if (isAddNewLink) return;
MyThumb thumb = e.Source as MyThumb;
Canvas.SetLeft(thumb, Canvas.GetLeft(thumb) + e.HorizontalChange);
Canvas.SetTop(thumb, Canvas.GetTop(thumb) + e.VerticalChange);
// Update links' layouts for active thumb
thumb.UpdateLinks();
}
// Event handler for creating new thumb element by left mouse click
// and visually connecting it to the myThumb2 element
void AdNewNode(Point nPosition,string nTitle, string nImage)
{
MyThumb newThumb = new MyThumb(
nTitle,
nImage,
nPosition,
onDragDelta);
myCanvas.Children.Add(newThumb);
}
void Window1_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
// If adding new action...
if (isAddNewAction)
{
AdNewNode(e.GetPosition(this), "ACTION", "/Images/gear_connection.png");
// resume common layout for application
isAddNewAction = false;
Mouse.OverrideCursor = null;
btnNewAction.IsEnabled = btnNewLink.IsEnabled = true;
e.Handled = true;
}
// Is adding new link and a thumb object is clicked...
if (isAddNewLink && e.Source.GetType() == typeof(MyThumb))
{
if (!isLinkStarted)
{
if (link == null || link.EndPoint != link.StartPoint)
{
Point position = e.GetPosition(this);
link = new LineGeometry(position, position);
//nodepath=new Path();
//nodepath.Stroke = Brushes.Pink;
//nodepath.Data = link; // Here line color is getting change but connectivity getting lost....
connectors.Children.Add(link);
//myCanvas.Children.Add(nodepath);
isLinkStarted = true;
linkedThumb = e.Source as MyThumb;
e.Handled = true;
}
}
}
}
// Handles the mouse move event when dragging/drawing the new connection link
void Window1_PreviewMouseMove(object sender, MouseEventArgs e)
{
if (isAddNewLink && isLinkStarted)
{
// Set the new link end point to current mouse position
link.EndPoint = e.GetPosition(this);
e.Handled = true;
}
}
// Handles the mouse up event applying the new connection link or resetting it
void Window1_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
// If "Add link" mode enabled and line drawing started (line placed to canvas)
if (isAddNewLink && isLinkStarted)
{
// declare the linking state
bool linked = false;
// We released the button on MyThumb object
if (e.Source.GetType() == typeof(MyThumb))
{
MyThumb targetThumb = e.Source as MyThumb;
// define the final endpoint of line
link.EndPoint = e.GetPosition(this);
// if any line was drawn (avoid just clicking on the thumb)
if (link.EndPoint != link.StartPoint && linkedThumb != targetThumb)
{
// establish connection
linkedThumb.LinkTo(targetThumb, link);
// set linked state to true
//nodepath = new Path();
//nodepath.Stroke = Brushes.Pink;
//nodepath.Data = link;
linked = true;
}
}
// if we didn't manage to approve the linking state
// button is not released on MyThumb object or double-clicking was performed
if (!linked)
{
// remove line from the canvas
//connectors.Children.Remove(link);
// clear the link variable
link = null;
}
// exit link drawing mode
isLinkStarted = isAddNewLink = false;
// configure GUI
btnNewAction.IsEnabled = btnNewLink.IsEnabled = true;
Mouse.OverrideCursor = null;
e.Handled = true;
}
//this.Title = "Links established: " + connectors.Children.Count.ToString();
}
// Event handler for enabling new thumb creation by left mouse button click
private void btnNewAction_Click(object sender, RoutedEventArgs e)
{
isAddNewAction = true;
Mouse.OverrideCursor = Cursors.SizeAll;
btnNewAction.IsEnabled = btnNewLink.IsEnabled = false;
}
private void btnNewLink_Click(object sender, RoutedEventArgs e)
{
isAddNewLink = true;
Mouse.OverrideCursor = Cursors.Cross;
btnNewAction.IsEnabled = btnNewLink.IsEnabled = false;
}
}
Please try to correct me where i'm doing wrong in this code.
In the code below I am trying to zoom the image via a mouse wheel. But the code is not working properly, it is just refreshing the panel but it does not resize it. Actually I am taking the image from memory stream that is created by another class called as decrypt. Complete Image is displayed properly but I am not able to performing zooming of the image using mousewheel event.
Plz help Me.
private void Form2_Load(object sender, EventArgs e)
{
this.Width = Screen.PrimaryScreen.WorkingArea.Width;
this.Height = Screen.PrimaryScreen.WorkingArea.Height;
this.CenterToScreen();
PicturePanel= new PictureBox();
PicturePanel.Dock = DockStyle.Fill;
//PicturePanel.SizeMode = PictureBoxSizeMode.AutoSize;
//PicturePanel.SizeMode = PictureBoxSizeMode.CenterImage;
PicturePanel.Focus();
//PicturePanel.MouseWheel += new System.Windows.Forms.MouseEventHandler(this.OnMouseWheel);
this.Controls.Add(PicturePanel);
View_codee v = new View_codee();
try
{
PicturePanel.Image = Image.FromStream(Decrypt.ms1);
}
catch (Exception ee)
{
MessageBox.Show(ee.Message);
}
this.Name = "";
}
protected override void OnMouseWheel(MouseEventArgs mea)
{
// Override OnMouseWheel event, for zooming in/out with the scroll wheel
if (PicturePanel.Image != null)
{
// If the mouse wheel is moved forward (Zoom in)
if (mea.Delta > 0)
{
// Check if the pictureBox dimensions are in range (15 is the minimum and maximum zoom level)
if ((PicturePanel.Width < (15 * this.Width)) && (PicturePanel.Height < (15 * this.Height)))
{
// Change the size of the picturebox, multiply it by the ZOOMFACTOR
PicturePanel.Width = (int)(PicturePanel.Width * 1.25);
PicturePanel.Height = (int)(PicturePanel.Height * 1.25);
// Formula to move the picturebox, to zoom in the point selected by the mouse cursor
PicturePanel.Top = (int)(mea.Y - 1.25 * (mea.Y - PicturePanel.Top));
PicturePanel.Left = (int)(mea.X - 1.25 * (mea.X - PicturePanel.Left));
}
}
else
{
// Check if the pictureBox dimensions are in range (15 is the minimum and maximum zoom level)
if ((PicturePanel.Width > (this.Width / 15)) && (PicturePanel.Height > (this.Height / 15)))
{
// Change the size of the picturebox, divide it by the ZOOMFACTOR
PicturePanel.Width = (int)(PicturePanel.Width / 1.25);
PicturePanel.Height = (int)(PicturePanel.Height / 1.25);
// Formula to move the picturebox, to zoom in the point selected by the mouse cursor
PicturePanel.Top = (int)(mea.Y - 0.80 * (mea.Y - PicturePanel.Top));
PicturePanel.Left = (int)(mea.X - 0.80 * (mea.X - PicturePanel.Left));
}
}
}
}
Source
Updated code by adding a new ImageProperty so you can set directly the Image;
public class PictureBox : System.Windows.Forms.UserControl
{
#region Members
private System.Windows.Forms.PictureBox PicBox;
private Panel OuterPanel;
private Container components = null;
private string m_sPicName = "";
#endregion
#region Constants
private double ZOOMFACTOR = 1.25; // = 25% smaller or larger
private int MINMAX = 5; // 5 times bigger or smaller than the ctrl
#endregion
#region Designer generated code
private void InitializeComponent()
{
this.PicBox = new System.Windows.Forms.PictureBox();
this.OuterPanel = new System.Windows.Forms.Panel();
this.OuterPanel.SuspendLayout();
this.SuspendLayout();
//
// PicBox
//
this.PicBox.Location = new System.Drawing.Point(0, 0);
this.PicBox.Name = "PicBox";
this.PicBox.Size = new System.Drawing.Size(150, 140);
this.PicBox.TabIndex = 3;
this.PicBox.TabStop = false;
//
// OuterPanel
//
this.OuterPanel.AutoScroll = true;
this.OuterPanel.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.OuterPanel.Controls.Add(this.PicBox);
this.OuterPanel.Dock = System.Windows.Forms.DockStyle.Fill;
this.OuterPanel.Location = new System.Drawing.Point(0, 0);
this.OuterPanel.Name = "OuterPanel";
this.OuterPanel.Size = new System.Drawing.Size(210, 190);
this.OuterPanel.TabIndex = 4;
//
// PictureBox
//
this.Controls.Add(this.OuterPanel);
this.Name = "PictureBox";
this.Size = new System.Drawing.Size(210, 190);
this.OuterPanel.ResumeLayout(false);
this.ResumeLayout(false);
}
#endregion
#region Constructors
public PictureBox()
{
InitializeComponent();
InitCtrl(); // my special settings for the ctrl
}
#endregion
#region Properties
private Image _pictureImage;
public Image PictureImage
{
get { return _pictureImage; }
set
{
if (null != value)
{
try
{
PicBox.Image = value;
_pictureImage = value;
}
catch (OutOfMemoryException ex)
{
RedCross();
}
}
else
{
RedCross();
}
}
}
/// <summary>
/// Property to select the picture which is displayed in the picturebox. If the
/// file doesn´t exist or we receive an exception, the picturebox displays
/// a red cross.
/// </summary>
/// <value>Complete filename of the picture, including path information</value>
/// <remarks>Supported fileformat: *.gif, *.tif, *.jpg, *.bmp</remarks>
///
[Browsable(false)]
public string Picture
{
get { return m_sPicName; }
set
{
if (null != value)
{
if (System.IO.File.Exists(value))
{
try
{
PicBox.Image = Image.FromFile(value);
m_sPicName = value;
}
catch (OutOfMemoryException ex)
{
RedCross();
}
}
else
{
RedCross();
}
}
}
}
/// <summary>
/// Set the frametype of the picturbox
/// </summary>
[Browsable(false)]
public BorderStyle Border
{
get { return OuterPanel.BorderStyle; }
set { OuterPanel.BorderStyle = value; }
}
#endregion
#region Other Methods
/// <summary>
/// Special settings for the picturebox ctrl
/// </summary>
private void InitCtrl()
{
PicBox.SizeMode = PictureBoxSizeMode.StretchImage;
PicBox.Location = new Point(0, 0);
OuterPanel.Dock = DockStyle.Fill;
OuterPanel.Cursor = System.Windows.Forms.Cursors.NoMove2D;
OuterPanel.AutoScroll = true;
OuterPanel.MouseEnter += new EventHandler(PicBox_MouseEnter);
PicBox.MouseEnter += new EventHandler(PicBox_MouseEnter);
OuterPanel.MouseWheel += new MouseEventHandler(PicBox_MouseWheel);
}
/// <summary>
/// Create a simple red cross as a bitmap and display it in the picturebox
/// </summary>
private void RedCross()
{
Bitmap bmp = new Bitmap(OuterPanel.Width, OuterPanel.Height, System.Drawing.Imaging.PixelFormat.Format16bppRgb555);
Graphics gr;
gr = Graphics.FromImage(bmp);
Pen pencil = new Pen(Color.Red, 5);
gr.DrawLine(pencil, 0, 0, OuterPanel.Width, OuterPanel.Height);
gr.DrawLine(pencil, 0, OuterPanel.Height, OuterPanel.Width, 0);
PicBox.Image = bmp;
gr.Dispose();
}
#endregion
#region Zooming Methods
/// <summary>
/// Make the PictureBox dimensions larger to effect the Zoom.
/// </summary>
/// <remarks>Maximum 5 times bigger</remarks>
private void ZoomIn()
{
if ((PicBox.Width < (MINMAX * OuterPanel.Width)) &&
(PicBox.Height < (MINMAX * OuterPanel.Height)))
{
PicBox.Width = Convert.ToInt32(PicBox.Width * ZOOMFACTOR);
PicBox.Height = Convert.ToInt32(PicBox.Height * ZOOMFACTOR);
PicBox.SizeMode = PictureBoxSizeMode.StretchImage;
}
}
/// <summary>
/// Make the PictureBox dimensions smaller to effect the Zoom.
/// </summary>
/// <remarks>Minimum 5 times smaller</remarks>
private void ZoomOut()
{
if ((PicBox.Width > (OuterPanel.Width / MINMAX)) &&
(PicBox.Height > (OuterPanel.Height / MINMAX)))
{
PicBox.SizeMode = PictureBoxSizeMode.StretchImage;
PicBox.Width = Convert.ToInt32(PicBox.Width / ZOOMFACTOR);
PicBox.Height = Convert.ToInt32(PicBox.Height / ZOOMFACTOR);
}
}
#endregion
#region Mouse events
/// <summary>
/// We use the mousewheel to zoom the picture in or out
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void PicBox_MouseWheel(object sender, MouseEventArgs e)
{
if (e.Delta < 0)
{
ZoomIn();
}
else
{
ZoomOut();
}
}
/// <summary>
/// Make sure that the PicBox have the focus, otherwise it doesn´t receive
/// mousewheel events !.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void PicBox_MouseEnter(object sender, EventArgs e)
{
if (PicBox.Focused == false)
{
PicBox.Focus();
}
}
#endregion
#region Disposing
/// <summary>
/// Die verwendeten Ressourcen bereinigen.
/// </summary>
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (components != null)
components.Dispose();
}
base.Dispose(disposing);
}
#endregion
}
private void Form2_Load(object sender, EventArgs e)
{
pictureBox1.PictureImage = Image.FromStream(Decrypt.ms1);
}
I have a control (System.Windows.Forms.ScrollableControl) which can potentially be very large. It has custom OnPaint logic. For that reason, I am using the workaround described here.
public class CustomControl : ScrollableControl
{
public CustomControl()
{
this.AutoScrollMinSize = new Size(100000, 500);
this.DoubleBuffered = true;
}
protected override void OnScroll(ScrollEventArgs se)
{
base.OnScroll(se);
this.Invalidate();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
var graphics = e.Graphics;
graphics.Clear(this.BackColor);
...
}
}
The painting code mainly draws "normal" things that move when you scroll. The origin of each shape that is drawn is offsetted by this.AutoScrollPosition.
graphics.DrawRectangle(pen, 100 + this.AutoScrollPosition.X, ...);
However, the control also contains "static" elements, which are always drawn at the same position relative to the parent control. For that, I just don't use AutoScrollPosition and draw the shapes directly:
graphics.DrawRectangle(pen, 100, ...);
When the user scrolls, Windows translates the entire visible area in the direction opposite to the scrolling. Usually this makes sense, because then the scrolling seems smooth and responsive (and only the new part has to be redrawn), however the static parts are also affected by this translation (hence the this.Invalidate() in OnScroll). Until the next OnPaint call has successfully redrawn the surface, the static parts are slightly off. This causes a very noticable "shaking" effect when scrolling.
Is there a way I can create a scrollable custom control that does not have this problem with static parts?
You could do this by taking full control of scrolling. At the moment, you're just hooking in to the event to do your logic. I've faced issues with scrolling before, and the only way I've ever managed to get everything to work smoothly is by actually handling the Windows messages by overriding WndProc. For instance, I have this code to synchronize scrolling between several ListBoxes:
protected override void WndProc(ref Message m) {
base.WndProc(ref m);
// 0x115 and 0x20a both tell the control to scroll. If either one comes
// through, you can handle the scrolling before any repaints take place
if (m.Msg == 0x115 || m.Msg == 0x20a)
{
//Do you scroll processing
}
}
Using WndProc will get you the scroll messages before anything gets repainted at all, so you can appropriately handle the static objects. I'd use this to suspend scrolling until an OnPaint occurs. It won't look as smooth, but you won't have issues with the static objects moving.
Since I really needed this, I ended up writing a Control specifically for the case when you have static graphics on a scrollable surface (whose size can be greater than 65535).
It is a regular Control with two ScrollBar controls on it, and a user-assignable Control as its Content. When the user scrolls, the container sets its Content's AutoScrollOffset accordingly. Therefore, it is possible to use controls which use the AutoScrollOffset method for drawing without changing anything. The Content's actual size is exactly the visible part of it at all times. It allows horizontal scrolling by holding down the shift key.
Usage:
var container = new ManuallyScrollableContainer();
var content = new ExampleContent();
container.Content = content;
container.TotalContentWidth = 150000;
container.TotalContentHeight = 5000;
container.Dock = DockStyle.Fill;
this.Controls.Add(container); // e.g. add to Form
Code:
It became a bit lengthy, but I could avoid ugly hacks. Should work with mono. I think it turned out pretty sane.
public class ManuallyScrollableContainer : Control
{
public ManuallyScrollableContainer()
{
InitializeControls();
}
private class UpdatingHScrollBar : HScrollBar
{
protected override void OnValueChanged(EventArgs e)
{
base.OnValueChanged(e);
// setting the scroll position programmatically shall raise Scroll
this.OnScroll(new ScrollEventArgs(ScrollEventType.EndScroll, this.Value));
}
}
private class UpdatingVScrollBar : VScrollBar
{
protected override void OnValueChanged(EventArgs e)
{
base.OnValueChanged(e);
// setting the scroll position programmatically shall raise Scroll
this.OnScroll(new ScrollEventArgs(ScrollEventType.EndScroll, this.Value));
}
}
private ScrollBar shScrollBar;
private ScrollBar svScrollBar;
public ScrollBar HScrollBar
{
get { return this.shScrollBar; }
}
public ScrollBar VScrollBar
{
get { return this.svScrollBar; }
}
private void InitializeControls()
{
this.Width = 300;
this.Height = 300;
this.shScrollBar = new UpdatingHScrollBar();
this.shScrollBar.Top = this.Height - this.shScrollBar.Height;
this.shScrollBar.Left = 0;
this.shScrollBar.Anchor = AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
this.svScrollBar = new UpdatingVScrollBar();
this.svScrollBar.Top = 0;
this.svScrollBar.Left = this.Width - this.svScrollBar.Width;
this.svScrollBar.Anchor = AnchorStyles.Right | AnchorStyles.Top | AnchorStyles.Bottom;
this.shScrollBar.Width = this.Width - this.svScrollBar.Width;
this.svScrollBar.Height = this.Height - this.shScrollBar.Height;
this.Controls.Add(this.shScrollBar);
this.Controls.Add(this.svScrollBar);
this.shScrollBar.Scroll += this.HandleScrollBarScroll;
this.svScrollBar.Scroll += this.HandleScrollBarScroll;
}
private Control _content;
/// <summary>
/// Specifies the control that should be displayed in this container.
/// </summary>
public Control Content
{
get { return this._content; }
set
{
if (_content != value)
{
RemoveContent();
this._content = value;
AddContent();
}
}
}
private void AddContent()
{
if (this.Content != null)
{
this.Content.Left = 0;
this.Content.Top = 0;
this.Content.Width = this.Width - this.svScrollBar.Width;
this.Content.Height = this.Height - this.shScrollBar.Height;
this.Content.Anchor = AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right;
this.Controls.Add(this.Content);
CalculateMinMax();
}
}
private void RemoveContent()
{
if (this.Content != null)
{
this.Controls.Remove(this.Content);
}
}
protected override void OnParentChanged(EventArgs e)
{
// mouse wheel events only arrive at the parent control
if (this.Parent != null)
{
this.Parent.MouseWheel -= this.HandleMouseWheel;
}
base.OnParentChanged(e);
if (this.Parent != null)
{
this.Parent.MouseWheel += this.HandleMouseWheel;
}
}
private void HandleMouseWheel(object sender, MouseEventArgs e)
{
this.HandleMouseWheel(e);
}
/// <summary>
/// Specifies how the control reacts to mouse wheel events.
/// Can be overridden to adjust the scroll speed with the mouse wheel.
/// </summary>
protected virtual void HandleMouseWheel(MouseEventArgs e)
{
// The scroll difference is calculated so that with the default system setting
// of 3 lines per scroll incremenet,
// one scroll will offset the scroll bar value by LargeChange / 4
// i.e. a quarter of the thumb size
ScrollBar scrollBar;
if ((Control.ModifierKeys & Keys.Shift) != 0)
{
scrollBar = this.HScrollBar;
}
else
{
scrollBar = this.VScrollBar;
}
var minimum = 0;
var maximum = scrollBar.Maximum - scrollBar.LargeChange;
if (maximum <= 0)
{
// happens when the entire area is visible
return;
}
var value = scrollBar.Value - (int)(e.Delta * scrollBar.LargeChange / (120.0 * 12.0 / SystemInformation.MouseWheelScrollLines));
scrollBar.Value = Math.Min(Math.Max(value, minimum), maximum);
}
public event ScrollEventHandler Scroll;
protected virtual void OnScroll(ScrollEventArgs e)
{
var handler = this.Scroll;
if (handler != null)
{
handler(this, e);
}
}
/// <summary>
/// Event handler for the Scroll event of either scroll bar.
/// </summary>
private void HandleScrollBarScroll(object sender, ScrollEventArgs e)
{
OnScroll(e);
if (this.Content != null)
{
this.Content.AutoScrollOffset = new System.Drawing.Point(-this.HScrollBar.Value, -this.VScrollBar.Value);
this.Content.Invalidate();
}
}
private int _totalContentWidth;
public int TotalContentWidth
{
get { return _totalContentWidth; }
set
{
if (_totalContentWidth != value)
{
_totalContentWidth = value;
CalculateMinMax();
}
}
}
private int _totalContentHeight;
public int TotalContentHeight
{
get { return _totalContentHeight; }
set
{
if (_totalContentHeight != value)
{
_totalContentHeight = value;
CalculateMinMax();
}
}
}
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
CalculateMinMax();
}
private void CalculateMinMax()
{
if (this.Content != null)
{
// Reduced formula according to
// http://msdn.microsoft.com/en-us/library/system.windows.forms.scrollbar.maximum.aspx
// Note: The original formula is bogus.
// According to the article, LargeChange has to be known in order to calculate Maximum,
// however, that is not always possible because LargeChange cannot exceed Maximum.
// If (LargeChange) == (1 * visible part of control), the formula can be reduced to:
if (this.TotalContentWidth > this.Content.Width)
{
this.shScrollBar.Enabled = true;
this.shScrollBar.Maximum = this.TotalContentWidth;
}
else
{
this.shScrollBar.Enabled = false;
}
if (this.TotalContentHeight > this.Content.Height)
{
this.svScrollBar.Enabled = true;
this.svScrollBar.Maximum = this.TotalContentHeight;
}
else
{
this.svScrollBar.Enabled = false;
}
// this must be set after the maximum is determined
this.shScrollBar.LargeChange = this.shScrollBar.Width;
this.shScrollBar.SmallChange = this.shScrollBar.LargeChange / 10;
this.svScrollBar.LargeChange = this.svScrollBar.Height;
this.svScrollBar.SmallChange = this.svScrollBar.LargeChange / 10;
}
}
}
Example content:
public class ExampleContent : Control
{
public ExampleContent()
{
this.DoubleBuffered = true;
}
static Random random = new Random();
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
var graphics = e.Graphics;
// random color to make the clip rectangle visible in an unobtrusive way
var color = Color.FromArgb(random.Next(160, 180), random.Next(160, 180), random.Next(160, 180));
graphics.Clear(color);
Debug.WriteLine(this.AutoScrollOffset.X.ToString() + ", " + this.AutoScrollOffset.Y.ToString());
CheckerboardRenderer.DrawCheckerboard(
graphics,
this.AutoScrollOffset,
e.ClipRectangle,
new Size(50, 50)
);
StaticBoxRenderer.DrawBoxes(graphics, new Point(0, this.AutoScrollOffset.Y), 100, 30);
}
}
public static class CheckerboardRenderer
{
public static void DrawCheckerboard(Graphics g, Point origin, Rectangle bounds, Size squareSize)
{
var numSquaresH = (bounds.Width + squareSize.Width - 1) / squareSize.Width + 1;
var numSquaresV = (bounds.Height + squareSize.Height - 1) / squareSize.Height + 1;
var startBoxH = (bounds.X - origin.X) / squareSize.Width;
var startBoxV = (bounds.Y - origin.Y) / squareSize.Height;
for (int i = startBoxH; i < startBoxH + numSquaresH; i++)
{
for (int j = startBoxV; j < startBoxV + numSquaresV; j++)
{
if ((i + j) % 2 == 0)
{
Random random = new Random(i * j);
var color = Color.FromArgb(random.Next(70, 95), random.Next(70, 95), random.Next(70, 95));
var brush = new SolidBrush(color);
g.FillRectangle(brush, i * squareSize.Width + origin.X, j * squareSize.Height + origin.Y, squareSize.Width, squareSize.Height);
brush.Dispose();
}
}
}
}
}
public static class StaticBoxRenderer
{
public static void DrawBoxes(Graphics g, Point origin, int boxWidth, int boxHeight)
{
int height = origin.Y;
int left = origin.X;
for (int i = 0; i < 25; i++)
{
Rectangle r = new Rectangle(left, height, boxWidth, boxHeight);
g.FillRectangle(Brushes.White, r);
g.DrawRectangle(Pens.Black, r);
height += boxHeight;
}
}
}