Ben
Ben

Reputation: 4319

Best way to create a floating object in c#

I want to make a system tray app that is used to "mask off" certain areas of the screen (this is for use to hide portions of some DJ software such as BPM counters, so the user can hide the BPM when practising, then press a key to reveal.) Effectively it's the equivalent of sticking a piece of gaffer tape to your monitor, but without the nasty residue!

The requirements are:

  1. The user should be able to draw a number of rectangles on screen where they want to mask off. (Maybe using a setup / edit mode)
  2. The rectangles will need to be always on top (kind of modal, but...)
  3. The user must be able to interact with the application below - i.e. mouse clicks and key presses should work on the DJ software
  4. The user should then be able to toggle between displaying and hiding the rectangles (without having to redraw each time

Not sure of the best way to approach this - thought about making the rectangles just a number of forms with no content or controls on. Is there a better (graphical way) to acheive this? Panels or rectangles or something?

Upvotes: 3

Views: 360

Answers (3)

Nikita B
Nikita B

Reputation: 3333

You can use a popup. For example, in wpf you can do the followning:

<Window x:Class="WpfApplication2.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow">
    <Grid>
        <ToggleButton x:Name="Button" Content="Show / Hide" Height="25"/>
        <Popup Width="300" Height="300" IsOpen="{Binding Path=IsChecked, ElementName=Button}" Placement="Absolute" PlacementRectangle="1,1,1,1"/>
    </Grid>
</Window>

It will show / hide a black popup of specified size (300x300) at the specified location (top left corner of the screen) whenever you press the button: enter image description here

You can make it movable and/or resizable, if you want to. I cannot think of an easier solution.

Upvotes: 0

User 12345678
User 12345678

Reputation: 7804

Ideally I would have liked to use a layered form but using a standard form is much easier to write and to present here at StackOverflow.

The UX is far from great but that doesn't matter, it's just to give you a general idea. You should feel free to adopt your own interface.

enter image description here

public sealed partial class GafferTape : Form
{
    private Point _startLocation = Point.Empty;
    private Point _cursorLocation = Point.Empty;
    private bool _drawing;
    private Rectangle _regionRectangle;
    private readonly List<Rectangle> _rectangles = new List<Rectangle>();

    public bool AllowDrawing { get; set; }

    public GafferTape()
    {
        InitializeComponent();

        //TODO: Consider letting the designer handle this.
        FormBorderStyle = FormBorderStyle.None;
        WindowState = FormWindowState.Maximized;
        TopMost = true;
        DoubleBuffered = true;
        ShowInTaskbar = false;
        Cursor = Cursors.Cross;
        BackColor = Color.White;
        Opacity = 0.4;
        TransparencyKey = Color.Black;

        //TODO: Consider letting the designer handle this.
        MouseDown += OnMouseDown;
        MouseUp += OnMouseUp;
        MouseMove += OnMouseMove;
        Paint += OnPaint;

        AllowDrawing = true;

    }

    private void OnMouseDown(object sender, MouseEventArgs mouseEventArgs)
    {
        //I don't allow the user to draw after the rectangles have been drawn. See: buttonInvert_Clic
        if (AllowDrawing)
        {
            _startLocation = mouseEventArgs.Location;
            _drawing = true;
        }
    }

    private void OnMouseUp(object sender, MouseEventArgs mouseEventArgs)
    {
        _drawing = false;
        DialogResult = DialogResult.OK;
        _rectangles.Add(_regionRectangle);
    }

    private void OnMouseMove(object sender, MouseEventArgs mouseEventArgs)
    {
        if (_drawing == false)
            return;

        _cursorLocation = mouseEventArgs.Location;

        _regionRectangle = new Rectangle(Math.Min(_startLocation.X, _cursorLocation.X),
                                         Math.Min(_startLocation.Y, _cursorLocation.Y),
                                         Math.Abs(_startLocation.X - _cursorLocation.X),
                                         Math.Abs(_startLocation.Y - _cursorLocation.Y));

        Invalidate();
    }

    private void OnPaint(object sender, PaintEventArgs paintEventArgs)
    {
        foreach (Rectangle rectangle in _rectangles)
            paintEventArgs.Graphics.FillRectangle(Brushes.Black, rectangle);

        paintEventArgs.Graphics.FillRectangle(Brushes.Black, _regionRectangle);
    }

    private void buttonInvert_Click(object sender, EventArgs e)
    {
        Opacity = 100;
        TransparencyKey = Color.White;
        AllowDrawing = false;
        Cursor = Cursors.Default;
    }
}

Upvotes: 3

Idle_Mind
Idle_Mind

Reputation: 39142

Here's an overview of how I would do it:

  1. Take a Screenshot of the Desktop.
  2. Display it in a Maximized, Borderless Form.
  3. Allow user to select the Rectangles on it; look up "Rubberbanding" and the Paint() event to draw more than one of them at a time.
  4. Add the selected Rectangles to a GraphicsPath and set the Region() property of a Form; this will literally remove the parts of the form where the rectangles are NOT.
  5. In CreateParams(), set the WS_EX_TRANSPARENT and WS_EX_NOACTIVATE flags (this allows mouse events to "fall through" the rectangles to apps below).
  6. Set TopMost() to True.
  7. Use the RegisterHotKey() to toggle the form on a keypress.

Upvotes: 0

Related Questions