Change cursor to cross while form is open (snippet

2020-07-18 03:34发布

I am trying to implement a software like "Gyazo", a snippet tool that takes a screenshot. The program begins (nothing appears on the screen, other than the cursor changing), the user clicks to point A, then drags to point B (drawing a transparent rectangle), releases the mouse, then the screenshot gets saved and the program closes.

The way I draw that transparent rectangle, is that I re-size and re-position a form with a 30% transparency. So the cursor is never on the form! In order to change the cursor, since it is outside of the form, I tried using:

[DllImport("user32.dll")]
static extern bool SetSystemCursor(IntPtr hcur, uint id);
[DllImport("user32.dll")]
static extern IntPtr LoadCursor(IntPtr hInstance, int lpCursorName);
private int CROSS = 32515;
private const uint NORMAL = 32512;

//and then calling
SetSystemCursor(LoadCursor(IntPtr.Zero, CROSS), NORMAL);

The problem I had with this code is that it is really buggy. When the form closes, the cursor doesn't change back to normal. I don't know how to revert the cursor properly. Also, reverting the cursor when the form is closed from the task manager will be impossible, correct ?

What other way would you suggest to change the cursor to cross in this case ?

Edit: Just to clarify, because I tried asking a similar question before which was marked as duplicate of this question and I deleted it, what I am trying to do is similar, but a lot different, because in the answer provided in that question, the solution provided in the answers, is to make a full-screen borderless form, set a screenshot of the desktop as the background of that form, and then crop a rectangle from that. Firstly, that solution "freezes" the screen, since all you see a photo of your desktop while the cropping takes place, and secondly, it is near impossible to handle multi-monitor setups that way. Plus it does extra and unnecessary work.

标签: c# winforms
3条回答
劳资没心,怎么记你
2楼-- · 2020-07-18 04:23

Make two forms. One for taking the fullscreen snapshot and other for cropping the required area. And after selecting the area, pass the values taken to the form containing the image and save it.

I'll give you an example in which you just add two forms without have to do anything in design

Form_ScreenShot

public delegate void CROP_PARAMS(Point pnt, Size sz);
public partial class Form_ScreenShot : Form
{
    Form_TransparentSelection transpSelect;
    PictureBox pBox;
    public Form_ScreenShot()
    {
        InitializeComponent();
        WindowState = FormWindowState.Maximized;
        FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
        pBox = new PictureBox() { Dock = DockStyle.Fill, Cursor = Cursors.Cross };
        pBox.MouseDown += pBox_MouseDown;
        Controls.Add(pBox);

        Bitmap bmp = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height);
        Graphics grp = Graphics.FromImage(bmp);
        grp.CopyFromScreen(0, 0, 0, 0, new Size(bmp.Width, bmp.Height), CopyPixelOperation.SourceCopy);
        pBox.Image = bmp;

    }

    void CropImage(Point startPoint, Size size)
    {
        Bitmap bmp = new Bitmap(size.Width, size.Height);
        Graphics grp = Graphics.FromImage(bmp);
        grp.DrawImage(pBox.Image, new Rectangle(0, 0, size.Width, size.Height), new Rectangle(startPoint, size), GraphicsUnit.Pixel);
        pBox.Image = bmp;
        bmp.Save("D:\\Check1.png", System.Drawing.Imaging.ImageFormat.Png);
    }

    void pBox_MouseDown(object sender, MouseEventArgs e)
    {
        transpSelect = new Form_TransparentSelection(e) {CropImage = CropImage};
        transpSelect.ShowDialog();
        Close();
    }

}

Now the Semi transparent Selecting form

Form_TransparentSelection

public partial class Form_TransparentSelection : Form
{
    PictureBox pbSelection;
    Point lastPoint;
    public CROP_PARAMS CropImage { get; set; }
    public Form_TransparentSelection(MouseEventArgs e)
    {
        InitializeComponent();
        WindowState = FormWindowState.Maximized;
        FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
        MouseMove += Form_TransparentSelection_MouseMove;
        MouseUp += Form_TransparentSelection_MouseUp;
        Cursor = Cursors.Cross;

        pbSelection = new PictureBox() { Size = MinimumSize = new Size(5, 5), Visible = false, BackColor = Color.LightGreen };
        Controls.Add(pbSelection);

        lastPoint = new Point(e.X, e.Y);
        pbSelection.Size = pbSelection.MinimumSize;
        pbSelection.Visible = true;
        pbSelection.Location = lastPoint;

        Opacity = .5;
        TransparencyKey = Color.LightGreen;
        BackColor = Color.Black;
    }

    void Form_TransparentSelection_MouseUp(object sender, MouseEventArgs e)
    {
        CropImage(pbSelection.Location, pbSelection.Size);
        Close();
    }

    void Form_TransparentSelection_MouseMove(object sender, MouseEventArgs e)
    {
        pbSelection.Width = e.X - lastPoint.X;
        pbSelection.Height = e.Y - lastPoint.Y;
    }
}
查看更多
ゆ 、 Hurt°
3楼-- · 2020-07-18 04:37

Try putting this on your Program.cs file

static class Program
{
    [DllImport("user32.dll")]
    static extern bool SetSystemCursor(IntPtr hcur, uint id);
    [DllImport("user32.dll")]
    static extern IntPtr LoadCursor(IntPtr hInstance, int lpCursorName);

    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    private static extern Int32 SystemParametersInfo(UInt32 uiAction, UInt32
    uiParam, String pvParam, UInt32 fWinIni);

    private static uint CROSS = 32515;
    private static uint NORMAL = 32512;
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        SetSystemCursor(LoadCursor(IntPtr.Zero, (int)NORMAL), CROSS);
        //Also other pointers to CROSS if you want
        Application.Run(new Form1());
        SystemParametersInfo(0x0057, 0, null, 0);
    }
}

this will revert it back to normal whenever the application ends or crashes..

So like changing from Normal to Cross, you can change whatever cursor you want to Arrow

It won't work when you stop the application (Ctrl+F5), because that will skip all the lines. But will work completely after publishing the application.

查看更多
Lonely孤独者°
4楼-- · 2020-07-18 04:38

Setting the cursor to its default value before exiting the application will fix the issue. This can be implemented in the Form.Closing event as:

private void Form_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
    // 'NORMAL' is the same constant that is in your code.
    SetSystemCursor(LoadCursor(IntPtr.Zero, NORMAL), NORMAL);
}

Edit: Killing the process through the "Kill Task" option in the task manager will trigger the Form.Closing event. There's no way to intercept a TerminateProcess() call, the one used with the "Kill Process" option.

查看更多
登录 后发表回答