Home > Software design >  Limit drag in single direction and to a specific location
Limit drag in single direction and to a specific location

Time:07-03

I am trying to make a custom notification control for winform. I'm currently trying to make it draggable to a specific direction (say right). Also I don't want the notification to go beyond the Initial location where it first appeared.

Currently I've written this code:

    private int cordX, cordY;
    private Point StartLocation;

    public void ShowNotification(string Message)
    {
        StartPosition = FormStartPosition.Manual;
        string nName;
        for (int i = 1; i < 11; i  )
        {
            nName = "Notification"   i.ToString();
            CustomNotification CstNotif = (CustomNotification)Application.OpenForms[nName];
            if (CstNotif == null)
            {
                Name = nName;
                cordX = Screen.PrimaryScreen.WorkingArea.Width - Width;
                cordY = Screen.PrimaryScreen.WorkingArea.Height - Height * i;
                Location = new Point(cordX, cordY);
                break;
            }
        }
        label1.Text = Message;
        Show();
    }

    private void CustomNotification_MouseDown(object sender, MouseEventArgs e)
    {
        StartLocation = e.Location;
    }

    private void CustomNotification_MouseMove(object sender, MouseEventArgs e)
    {
        if (e.Button == MouseButtons.Left)
        {
            Location = new Point(Location.X - StartLocation.X   e.X, cordY);
        }
    }

This makes the notification draggable but I'm confused in how to restrict it in only one direction.

You all have probably seen the native windows 10 notifications. I'm basically trying to replicate that functionality.

CodePudding user response:

After tinkering a lot, I found a solution that is somewhat near to my expectation. What I did is that I introduced a new variable that records the Starting location of the notification which is compared with current location during a mouse up event.

Here is the new code:

private int cordX, cordY;
private Point StartCursorLocation;
private Point StartLocation;

private void Notification_Load(object sender, EventArgs e)
{
    StartLocation = Location;
}

private void Notification_MouseDown(object sender, MouseEventArgs e)
{
    StartCursorLocation = e.Location;
}

private void Notification_MouseUp(object sender, MouseEventArgs e)
{
    if (Location.X < StartLocation.X)
    {
        Location = StartLocation;
    }
}

private void Notification_MouseMove(object sender, MouseEventArgs e)
{
    if (e.Button == MouseButtons.Left)
    {
        int NewX = Location.X - StartCursorLocation.X   e.X;
        if (Location.X >= StartLocation.X || NewX >= StartLocation.X)
        {
            Location = new Point(NewX, cordY);
        }
    }
}

It is creating a type of spring effect which brings the notification back to its initial location if tried to pull it in opposite direction. I won't accept this as an answer and hope if somebody can provide an accurate solution to this problem.

Thank you.

CodePudding user response:

It looks like you've made some nice progress with the answer you posted! You mentioned that there were some lingering issues with the positioning. Here's a solution I've been looking at that fixes that issue, and has these additional features:

  • The notifications are in a dockable container (see screen-docked image at the very bottom).
  • When hovered, the text highlights and gives an [X] to delete (similar to the native version).
  • When swiped less than 3/4 width, snaps back to original location when the mouse is released.
  • When swiped to more than 3/4 width, deletes the message.
  • Recalculates positions when the number of messages changes.

Here are a few of the visual states:

normal, hovered, swiped


The CustomNotification class has a private constructor and a static Show method:

public partial class CustomNotification : UserControl
{
    static List<CustomNotification> CurrentNotifications { get; } = new List<CustomNotification>();
    private CustomNotification(string message)
    {
        InitializeComponent();
        labelMessage.Text = message;
        buttonDelete.Click  = (sender, e) =>
        {
            Dispose();
        };
    }
    public static void Show(IWin32Window owner, string message)
    {
        var notification = new CustomNotification(message);
        if (owner is Control control)
        {
            control.Controls.Add(notification);
            notification.Width = control.ClientRectangle.Width;
            CurrentNotifications.Add(notification);
            RecalcLocations();
        }
    }
    // Called when the number of messages changes
    private static void RecalcLocations()
    {
        for (int i = 0; i < CurrentNotifications.Count; i  )
        {
            var notification = CurrentNotifications[i];
            notification.Location = new Point(0, (notification.Height   10) * i);
        }
    }
    // Capture mouse down X position IN SCREEN COORDINATES
    protected override void onm ouseDown(MouseEventArgs e)
    {
        base.OnMouseDown(e);
        _mouseDownLocation = Location;
        _mouseDownX = PointToScreen(e.Location).X;
    }
    // Most of the issues are solved by using screen coordinates.
    protected override void onm ouseMove(MouseEventArgs e)
    {
        base.OnMouseMove(e);
        if (MouseButtons == MouseButtons.Left)
        {
            var delta = Math.Max(0, PointToScreen(e.Location).X - _mouseDownX);
            Location = new Point(delta, Location.Y);
        }
    }
    // Snap back or delete
    protected override void onm ouseUp(MouseEventArgs e)
    {
        base.OnMouseUp(e);
        var delta = Math.Max(0, PointToScreen(e.Location).X - _mouseDownX);
        if(delta > (3 * Width) / 4)
        {
            // Delete the message if hard swipe right.
            Dispose();
        }
        else
        {
            // Put it back where it was
            Location = _mouseDownLocation;
        }
    }
    // Hover behavior
    protected override void onm ouseEnter(EventArgs e)
    {
        base.OnMouseEnter(e);
        buttonDelete.Visible = true;
        labelMessage.ForeColor = Color.LightYellow;
    }
    protected override void onm ouseLeave(EventArgs e)
    {
        base.OnMouseLeave(e);
        if(null == GetChildAtPoint(PointToClient(MousePosition)))
        {
            buttonDelete.Visible = false;
            labelMessage.ForeColor = Color.White;
        }
    }
    public new void Dispose()
    {
        CurrentNotifications.Remove(this);
        RecalcLocations();
        base.Dispose();
    }
    private Point _mouseDownLocation;
    private int _mouseDownX;
}

Providing a container for these messages to display inside of makes them easily dockable and makes the swipe behavior look right.

public partial class CustomNotificationContainer : Form
{
    public CustomNotificationContainer()
    {
        InitializeComponent();
        checkBoxDocked.CheckedChanged  = (sender, e) =>
        {
            if(checkBoxDocked.Checked)
            {
                var x =
                    Screen.PrimaryScreen.WorkingArea.X  
                    Screen.PrimaryScreen.WorkingArea.Width -
                    Width;

                Location = new Point(x, Screen.PrimaryScreen.WorkingArea.Y);
                Height = Screen.PrimaryScreen.WorkingArea.Height;
            }
            else
            {
                Location = _defaultPos;
                Size = _defaultSize;
            }
        };
        textBoxNewMessage.KeyDown  = (sender, e) =>
        {
            switch (e.KeyData)
            {
                case Keys.Enter:
                    e.SuppressKeyPress = true;
                    CustomNotification.Show(this, textBoxNewMessage.Text);
                    textBoxNewMessage.Clear();
                    break;
            }
        };
    }
    private Point _defaultPos;
    private Size _defaultSize;
    protected override void OnHandleCreated(EventArgs e)
    {
        base.OnHandleCreated(e);
        _defaultPos = Location;
        _defaultSize = Size;
        CustomNotification.Show(this, "StackOverflow sent you a message");
        CustomNotification.Show(this, "IVSoftware wants your vote");
    }
}

Here's what it looks like when the [X] Docked checkbox is activated:

Docked


Thanks for the brain teaser! I hope this gives you a few ideas to try.

  • Related