4

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.

Community
  • 1
  • 1
dimitris93
  • 4,155
  • 11
  • 50
  • 86
  • I suggest 100% opacity and copying the screen into the form to display it seemlessly (without a border as well). It seems to cover all corners. – SimpleVar Apr 20 '15 at 01:55
  • 1
    This looks _exactly_ like the question you posted a day or so ago, and for which you already got a lot of feedback. Never mind how rude it is to just delete and then repost the exact same question, what makes you think you'd get anything materially different in the way of responses? It's not like the state of programming has changed significantly in the last 24 hours. – Peter Duniho Apr 20 '15 at 01:56
  • @YoryeNathan that will not achieve what I am trying to do, because if will "freeze" the screen while taking a screen and also it will be near impossible to handle multi monitor setups. I have seen the solution you suggest long ago in [this question](http://stackoverflow.com/questions/3123776/net-equivalent-of-snipping-tool) – dimitris93 Apr 20 '15 at 01:58
  • For future reference: if you disagree with the disposition of "closed-as-duplicate", the correct follow-up is to edit **that** question, explaining in detail why your question is not in fact a duplicate and then propose the question be re-opened. If the community agrees, the question will be re-opened, without the loss of all of the discussion so far, and _with_ the improvement of the question itself (which clearly didn't happen here...we are back to square one again with your question now). As things stand you run the risk of this question being closed again, for the same reasons. – Peter Duniho Apr 20 '15 at 02:22
  • @Shiro Is the area expected to change during the area selection? Does it matter, even? You can return to 30% opacity after you don't need the cross-hair mouse any more (after area selection is done), and in any case you can have the copied image from the area refreshed at a certain rate if you really want to. – SimpleVar Apr 20 '15 at 02:30
  • @YoryeNathan Yes it does matter to me because it doesn't achieve what I am trying to do... Plus, it does extra and unnecessary work, and makes it harder (probably impossible) to deal with multi-screen setups (especially if the monitors have different resolutions) as I mention in my question... :/ So that's 3 reasons right there – dimitris93 Apr 20 '15 at 02:34
  • 1
    "More work" is an invalid reason, because the option of doing less work results with an unsolved problem, or you wouldn't come here. Dealing with multi-screen setups can be done this way as well (yes, its more work). Refreshing it at 60fps can also be done (not that much, but still more work). If that doesn't suit you, you *probably* need to look for a different technology than C#. – SimpleVar Apr 20 '15 at 02:38
  • @YoryeNathan What I was trying to do was indeed impossible in C# in a fashionable way. Because fully transparent forms, don't fire up mouse events, and what you draw on them is fully transparent as well... so its invisible. The only way I could kind of do it, was to have a form and resize that as the "rectangle", and that brings more problems because the cursor is not on the form... so you have to use a global mouse hook, plus the cursor doesn't change. I saw the source code of gyazo and I ended up doing in in visual C++ in a low-level fashion. You were right C# wasn't the technology for that – dimitris93 Apr 24 '15 at 19:53

3 Answers3

1

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.

Abdul Saleem
  • 10,098
  • 5
  • 45
  • 45
  • I would have to change `point`, `hand`, `loading` and all the cursors to `cross`, and then how would i revert them since they are all overwritten as `cross` ? – dimitris93 Apr 20 '15 at 03:13
  • It won't work when you stop the application (Ctrl+F5), because that will skip all the lines. But will work after publishing the application – Abdul Saleem Apr 20 '15 at 03:41
0

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;
    }
}
Abdul Saleem
  • 10,098
  • 5
  • 45
  • 45
  • I am sorry but I mention it in my question that I don't want to do this by taking a screenshot of the background – dimitris93 Apr 20 '15 at 02:32
  • Is that just because for use in multi monitors? – Abdul Saleem Apr 20 '15 at 02:38
  • If so, you can do like 0 opacity forms on number of monitors you're connected, which will change the cursor on all the monitors and then crop. Do you want an edit for something like that? – Abdul Saleem Apr 20 '15 at 02:41
  • And also can prevent the UI from freezing. But you can't do anything else until you complete your dragging.. – Abdul Saleem Apr 20 '15 at 02:43
  • But 0.01 will do. Looks almost like Zero Opacity – Abdul Saleem Apr 20 '15 at 02:45
  • Opacity can be brought down upto 0.004 for the cursor change – Abdul Saleem Apr 20 '15 at 02:48
  • I really dislike having opacity on the whole screen, even a small amount of it... so far I was doing exactly that. I am just trying to find a better way. Just like "Gyazo" software works. There has to be a workaround that doesn't require 0.004 opacity, and still does what I need (changing the cursor to cross) – dimitris93 Apr 20 '15 at 02:48
  • If you want to use multiple monitors, you can call the instances to be launced on every monitor, or if the monitors are side by side configured, and if you know the sides, you can fill the forms all of one – Abdul Saleem Apr 20 '15 at 02:49
  • That doesn't solve the "0 opacity with cross cursor" problem though... The best idea I had so far is to keep a 4x4 form of 0.004 opacity and change its location so that it is centered in the cursor, so that the cursor changes to cross, since it is `on top a form` – dimitris93 Apr 20 '15 at 02:50
  • What's the problem you have with SetSystemCursor? – Abdul Saleem Apr 20 '15 at 03:03
  • change system cursor changes `X` cursor icon to `Y`. So I would have to change `point`, `hand` and all the cursors to `cross`, and then how would i revert them since they are all overwritten as `cross` ? As we speak my cursor is bugged and I don't know how to change it beside restarting my computer. Also, what would I do if the process is terminated from the task bar ? – dimitris93 Apr 20 '15 at 03:07
  • what is the IntPtr for Ibeam? – Abdul Saleem Apr 20 '15 at 03:12
-1

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.

  • that event doesn't trigger when the user closes the application from the task manager, so I want to avoid this completely, because, as I said in my question, `SetSystemCursor(...)` can be a super buggy piece of code, which can cause the user to restart the whole computer to revert the cursor back to normal – dimitris93 Apr 20 '15 at 02:15
  • 2
    @Shiro: to be clear: it's not the `SetSystemCursor()` function itself that is "super buggy", but rather it is programs that use it in appropriately which are. – Peter Duniho Apr 20 '15 at 02:23