Ok, I'm having an issue that I'm not even sure exactly what is happening. Basically: I have a mouseup event for clicking on a button. That event will remove 1 button and physically move all the buttons on the screen to fill the gap. So if you have 2 rows of 2 buttons
Button1 Button2
Button3 Button4
and you click Button1, it moves the last 3 so you now have
Button2 Button3
Button4
Ok, so, at the end of this event handler it takes a screenshot and saves it (replacing the previous image of buttons 1-4 with the new image of buttons 2-4).
The event handler looks like this
public void Handle_Button_MouseUp(object sender, MouseEventArgs e)
{
//Get rid of the button that was clicked
...some unnecessary to the question code here...
//Rearrange the remaining buttons to fill the gap
Rearrange_Buttons_In_Tray(element);
//Save the screenshot
imageBox.Image = SavePanel1Screenshot();
}
The screenshot code is
public Image SavePanel1Screenshot()
{
int BorderWidth = (this.Width - this.ClientSize.Width) / 2;
int TitlebarHeight = this.Height - this.ClientSize.Height - BorderWidth;
Rectangle rect = new Rectangle(0, 0, panel1.Width, panel1.Height);
Bitmap bmp = new Bitmap(panel1.Width, panel1.Height, PixelFormat.Format32bppArgb);
Graphics g = Graphics.FromImage(bmp);
g.CopyFromScreen(this.Left + panel1.Left + BorderWidth, this.Top + panel1.Top + TitlebarHeight, 0, 0, bmp.Size, CopyPixelOperation.SourceCopy);
Image screenShot;
screenShot = (Image)bmp;
return screenShot;
}
.
public void Rearrange_Buttons_In_Tray(int element)
{
for (int i = element; i < buttonList.Count; i++)
{
Place_Button_In_Tray(buttonList[i].buttonSaved, i, true);
buttonList[i].element = i;
}
}
Cleaned out some unnecessary to the question parts to avoid clutter. Sorry for the messed up indentation. NOTE: The buttons are not IN the panel, just on top of it. I just use the panel for measurement and aesthetic purposes
private void Place_Button_In_Tray(Button button, int element, bool isReArrange)
{
button.Width = bigButtonWidth;
button.Height = bigButtonHeight;
Image buttonImage = button.Image;
int numButtonsHoriz = panel1.Width / button.Width;
int numButtonsVerti = panel1.Height / button.Height;
int extraSpaceHoriz = (panel1.Width % button.Width) / (numButtonsHoriz);
int extraSpaceVerti = (panel1.Height % button.Height) / numButtonsHoriz;
int buttonCount;
if (!isReArrange)
buttonCount = buttonList.Count - 1;
else
buttonCount = element;
if ((buttonCount) < numButtonsHoriz)
{
button.Location = new Point(panel1MinX + (button.Width * (buttonCount)) + extraSpaceHoriz, (panel1MinY + extraSpaceVerti));
}
else
{
int newLine = (buttonCount) % numButtonsHoriz;
button.Location = new Point(panel1MinX + (button.Width * (newLine)) + extraSpaceHoriz, ((panel1MinY + extraSpaceVerti) + (button.Height * ((buttonCount) / 2) - ((buttonCount) % 2))));
}
And now my problem: The image is of a blank screen. It's not that it isn't taking the picture- it's taking the picture before buttons 2-4 are visible on the screen (I can visibly see this happen as I step through the program. At the time the screenshot is taken, the screen is completely blank. Only after it takes do the buttons reappear on the screen)! For some reason, they are not rendering until AFTER the event handler is finished. Once the final piece of code in the event handler is done (the screenshot saving), the buttons all reappear in their respective spots, despite the fact that the visibility of the buttons is not modified at all during this entire process (thus they remain visible the entire time).
I'm a little confused as to what exactly is happening and, more importantly, how to go about getting that screenshot after the event handler takes place. >_> Does anyone have any suggestions on what might be going on and, more importantly, how to get that screenshot?
EDIT: My description is somewhat difficult to understand. I do apologize. It's hard to articulate exactly what I'm trying to do and what is happening. Here's a more compact and to the point version:
Basically, I'm only trying to hide 1 button out of 4. The other 3 on the screen are moved to new locations. For some reason, when they get moved, they vanish from the screen. They don't reappear until after the mouseup function completes, despite me never having modified whether they are visible or not. I only change their location. I want the screenshot to contain those 3 buttons, but for some reason they aren't popping back into existence until after the entire mouseup function ends. >_> So my screenshot is of an empty screen, devoid of buttons
It's a bit confusing what you are describing. I clicked a button to hide it, run your screen shot code, and the image did not show the button.
Anyway, to "delay" the screen shot to after the event is called, you can try using BeginInvoke:
this.BeginInvoke(new Action(() => { imageBox.Image = SavePanel1Screenshot(); }));
I think you need to call a Refresh after moving your controls:
if ((buttonCount) < numButtonsHoriz) {
button.Location = new Point(panel1MinX + (button.Width * (buttonCount)) + extraSpaceHoriz, (panel1MinY + extraSpaceVerti));
} else {
int newLine = (buttonCount) % numButtonsHoriz;
button.Location = new Point(panel1MinX + (button.Width * (newLine)) + extraSpaceHoriz, ((panel1MinY + extraSpaceVerti) + (button.Height * ((buttonCount) / 2) - ((buttonCount) % 2))));
}
panel1.Refresh();
You're doing your work in the UI thread. See How to force Buttons, TextBoxes to repaint on form after a MessageBox closes in C#. I'd suggest you move the screenshot to a background thread if you can.
You may also need to wait until the buttons have rendered, either using the blunt expedient of sleeping for 100ms or so, or by investigating the Paint event and using some sort of flag to indicate that both required events have occurred and the screenshot can be taken.
Alternatively, you could force it to redraw: How do I call paint event?
as long as you only need the pixels of your own form ...
private void button1_Click(object sender, EventArgs e)
{
//Button magic
button1.Visible = false;
button2.Location = button1.Location;
Bitmap bmp = new Bitmap(this.Width, this.Height);
this.DrawToBitmap(bmp, new Rectangle(0, 0, this.Width, this.Height));
pictureBox1.Image = bmp;
//or do whatever you want with the bitmap
}
Related
EDIT: I've tried to solve this for over a week and I have searched here and elsewhere as well.
I'd like to begin with explaining that I am a hobby programmer and have nobody to ask for help. I can only read the books I buy and guides on the internet so I am sorry if the question is beyond stupid.
The case: I have a "Windows Form Application", the form itself is 500x500 in size and in it I have a "Picture Box"(size 400x400, loc 0,0).
I am aware that Thread.Sleep hangs the interface because it is not multi threaded.
The problem I suffer is that "OnPaint" is invoked when I launch the program.
If I add a button (dragged from the toolbox)then "OnPaint" is invoked TWO times and so on, confirmed up to 4 buttons. That is not the worst part however. Just mousing over the button (any button) invokes "OnPaint". As you can see, "Invalidate()" is not inserted yet because I can not even click the buttons yet.
How do I prevent this? What is the problem? What should I have done differently?
I mean, it does what I want just not when I want to.
Code:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
protected override void OnPaint(PaintEventArgs e)
{
int radie = 25;
Random random = new Random();
Graphics graphics = pictureBox1.CreateGraphics();
Color color = Color.Black;
SolidBrush brush = new SolidBrush(color);
for (int i = 2500; i > 0; i--)
{
color = Color.FromArgb(random.Next(0, 101), random.Next(0, 256), random.Next(0, 256), random.Next(0, 256));
brush.Color = color;
graphics.FillEllipse(brush, random.Next(0, 400 - radie * 2), random.Next(0, 400 - radie * 2), radie * 2, radie * 2);
Thread.Sleep(1);
}
}
}
For those who wonder what it does it just fills the picture box with circles of different colors and opacity. edit: Its not filled instantly, its supposed to look "pretty".
Thanks in advance!
You could set the form's ControlStyle.
In the form .ctor or other appropriate startup event, add:
this.SetStyle(ControlStyles.DoubleBuffer, true);
this.SetStyle(ControlStyles.UserPaint, true);
this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
I have created a custom ListView control to suit my needs and I'm having an issue that causes the ListView not to show any contents (not drawing anything, just white) when the form first loads.
If I resize the form or click on my control (anything that forces a repaint on the ListView) then it shows up as expected.
As a side note, it used to work just fine until I made a small change today and rebuilt the control. I removed all the changes I made and rebuilt again but the issue still happens. Any ideas as to why it will not show up (paint) when the form is first loaded?
This is what I use to do the custom drawing on my custom ListView control...
protected override void OnDrawItem(DrawListViewItemEventArgs e)
{
Image image = e.Item.ImageList.Images[e.Item.ImageIndex];
Size textSize = new Size((int)e.Graphics.MeasureString(e.Item.Text, e.Item.Font).Width, (int)e.Graphics.MeasureString(e.Item.Text, e.Item.Font).Height);
//Get the area of the item to be painted
Rectangle bounds = e.Bounds;
bounds.X = 0;
bounds.Width = this.Width;
//Set the spacing on the list view items
int hPadding = 0;
int vPadding = 0;
IntPtr padding = (IntPtr)(int)(((ushort)(hPadding + bounds.Width)) | (uint)((vPadding + bounds.Height) << 16));
SendMessage(this.Handle, (uint)ListViewMessage.LVM_SETICONSPACING, IntPtr.Zero, padding);
//Set the positions of the image and text
int imageLeft = (bounds.Width / 2) - (image.Width / 2);
int imageTop = bounds.Top + 3;
int textLeft = (bounds.Width / 2) - (textSize.Width / 2);
int textTop = imageTop + image.Height;
Point imagePosition = new Point(imageLeft, imageTop);
Point textPosition = new Point(textLeft, textTop);
//Draw background
using (Brush brush = new SolidBrush(e.Item.BackColor))
e.Graphics.FillRectangle(brush, bounds);
//Draw selected
if (e.Item.Selected)
{
using (Brush brush = new SolidBrush(m_SelectedColor))
e.Graphics.FillRectangle(brush, bounds);
}
//Draw image
e.Graphics.DrawImage(image, imagePosition);
//Draw text
e.Graphics.DrawString(e.Item.Text, e.Item.Font, new SolidBrush(e.Item.ForeColor), textPosition);
}
I also set the following things in the Constructor of my custom control...
public MyListView()
{
this.DoubleBuffered = true;
this.OwnerDraw = true;
this.View = View.LargeIcon;
this.Cursor = Cursors.Hand;
this.Scrollable = false;
}
I also inherit the ListView class...
public class MyListView : ListView
{
//All my source
}
You need to set the set the control to redraw itself when resized. So add this code in constructor of your control:
this.ResizeRedraw = true;
My apologies:
Doing the below reset my event handlers and the problem went away. However, once I hooked them up, I found the Resize event handler that I used to set the ColumnWidth was causing the issue. Why setting the ColumnWidth is causing this, I do not know.
Arvo Bowen's comment on this answer fixed it for me also (.NET 4.8 Framework, VS2022). To be clear, not requiring this.ResizeRedraw = true; from the answer.
So after much headache and time I found that I had absolutely nothing wrong with my control. It was your answer that made me create another control just like my existing one and test it. To my surprise it worked great. I simply copied my existing non-working control and pasted it on the form and then new one worked! Sometimes VS just does weird things... Somehow I managed to muck up the control's creation object or something..
I use the global mouse hooks from this page in a project. I want the mouse to move a specified path when it is clicked, but interupted if I let go of the mouse button, but for some reason the HookManager_MouseUp won't fire until round about 175 loops in my for-loop (forloop is for moving the mouse). (That should be about 0.4 seconds). No matter how long I press the mousebutton it will only stop the loop after those 0.4 seconds. If I wait longer than that it will stop the loop immediately when I let go of the mousebutton.
public void MoveMouse()
{
btm = new Bitmap(2000, 2000);
for (int i = 0; i < BézierPointList.Count; i++)
{
if (!run) { break; }
this.Cursor = new Cursor(Cursor.Current.Handle);
Cursor.Position = new Point((int)BézierPointList[i].X(), (int)BézierPointList[i].Y());
Graphics g = Graphics.FromImage(btm);
g.DrawRectangle(new Pen(Color.Red), (int)BézierPointList[i].X(), (int)BézierPointList[i].Y(), 1, 1);
Application.DoEvents();
System.Threading.Thread.Sleep(2);
}
CreateGraphics().DrawImage(btm, 0, 0);
}
(The graphics are for debugging).
Is there some way to update the events, or something else I can do to fix this? Thanks! (C#)
Edit:
My Form1.Designer.cs contains this code (among other)
Gma.UserActivityMonitor.HookManager.MouseDown += HookManager_MouseDown;
Gma.UserActivityMonitor.HookManager.MouseUp += HookManager_MouseUp;
And the methods HookManager_MouseUp and HookManager_MouseDown enters MoveMouse() and brakes it. The delay happens before HookManager_MouseUp (HookManager_MouseUp breaks the loop pretty much instantaneosly).
I am making a program where you bassicly move from tile to tile in windows forms.
So in order to do that, I wanted to use panels each panel has a tag. To detect collision.
So I have an image of my map. and I divided into multiple tiles. However now I have to drag 900 tiles onto panels.
This isn't very effective in 2 ways. First loading 900 textures isn't really a smart idea. Also it would take ages. So i wanted to use a spritesheet or tilemap. But how would I do that in winforms. I believe I have seen some people use a grid view or whatever. However im not sure how to do what I want to do.
What would be the best solution?
Thanks in advance!
For any serious gaming project WinForms is not the best platform. Either WPF or XNA or Unity are able to deliver high performance use of DirectX.
But since you want to do it in Winforms here is a way to do it.
It creates a whopping number of 900 PictureBoxes and loads each with a fraction of an source image:
private void Form1_Load(object sender, EventArgs e)
{
int tileWidth = 30;
int tileHeight = 30;
int tileRows = 30;
int tileCols = 30;
using (Bitmap sourceBmp = new Bitmap("D:\\900x900.jpg"))
{
Size s = new Size(tileWidth, tileHeight);
Rectangle destRect = new Rectangle(Point.Empty, s);
for (int row = 0; row < tileRows; row++)
for (int col = 0; col < tileCols; col++)
{
PictureBox p = new PictureBox();
p.Size = s;
Point loc = new Point(tileWidth * col, tileHeight * row);
Rectangle srcRect = new Rectangle(loc, s);
Bitmap tile = new Bitmap(tileWidth, tileHeight);
Graphics G = Graphics.FromImage(tile);
G.DrawImage(sourceBmp, destRect, srcRect, GraphicsUnit.Pixel);
p.Image = tile;
p.Location = loc;
p.Tag = loc;
p.Name = String.Format("Col={0:00}-Row={1:00}", col, row);
// p.MouseDown += p_MouseDown;
// p.MouseUp += p_MouseUp;
// p.MouseMove += p_MouseMove;
this.Controls.Add(p);
}
}
}
When I tried it I was a bit worried about perfomance, but..
This takes under 1 second to load on my machine.
Starting the programm adds 10MB to VS memory usage. That is like nothing.
For a fun project this will do; for best performance one might use Panels but these will have to be filled and refilled in the Paint event. This solution saves you the hassle and since you don't change the tile picture all the time this works well enough.
Pleae note: I have added a Name and a Tag to each PictureBox, so you can later refer to it. These both contain info about the original position of the Picturebox. The Name looks like this: Col=23-Row=02 and the Tag is the original Location object.
Also: Dynamically added controls take a little extra to script since you can't create their method bodies in the designer. Instead you add them like above. In doing so Intellisense and the Tab key are your best friends..
I have added three event handlers for a few mouse events. When you uncomment them you will have to add the methods like e.g. this:
void p_MouseMove(object sender, MouseEventArgs e)
{
throw new NotImplementedException();
}
But maybe you want to use other events to play like Drag&Drop or keyboard events..
There are two ways to refer to these tiles. Maybe you want to try and/or use both of them: You can loop over the form's controls with a
foreach (Control ctl in this.Controls)
{ if (ctl is PictureBox ) this.Text = ((PictureBox)ctl).Name ; }
It tests for the right type and then casts to PictureBox. As an example it displays the name of the tile in the window title.
Or you can have a variable and set it in the MouseDown event:
PictureBox currentTile;
void p_MouseDown(object sender, MouseEventArgs e)
{
currentTile = (PictureBox ) sender;
}
As the application starts, I'd like my WPF window to automatically snap to the right edge of the screen. Is there a way to do that? I also want to be able to retain its dimensions. So, unlike the snapping behavior that happens when you drag a window to the edge of the screen, causing the window to resize to either a portion of the screen or full screen, I want my window to simply snap to the edge at a certain location by default or if dragged by the user to a specific location afterwards, without resizing. I still want to retain the ability of the user to drag the window away from the edge.
Is there anything like that already implemented or would I have to create my own behavior schema? I tried numerous search keyword combinations, but couldn't find anything similar to what I'm doing. Some of the searches included disabling snapping behavior or providing snapping behavior, but nothing in the way I described above.
EDIT:
I haven't been able to find a ready solution, so I wrote my own. This solution is based on BenVlodgi's suggestions, so I thank him for helping me out. This is a very rough implementation and still requires a lot of polishing and better code techniques, but it works and it's a good base for anyone wanting to try this. It's incredibly simple and works very well with WPF. The only limitation of this implementation is that I haven't tried getting it to work with two screens yet, but it's incredibly simple (I'm just not going to have time for it and I don't need that functionality at this point). So, here's the code and I hope that it helps someone out there:
public partial class MainWindow : Window
{
// Get the working area of the screen. It excludes any dockings or toolbars, which
// is exactly what we want.
private System.Drawing.Rectangle screen =
System.Windows.Forms.Screen.PrimaryScreen.WorkingArea;
// This will be the flag for the automatic positioning.
private bool dragging = false;
// The usual initialization routine
public MainWindow()
{
InitializeComponent();
}
// Wait until window is lodaded, but prior to being rendered to set position. This
// is done because prior to being loaded you'll get NaN for this.Height and 0 for
// this.ActualHeight.
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// Sets the initial position.
SetInitialWindowPosition();
// Sets the monitoring timer loop.
InitializeWindowPositionMonitoring();
}
// Allows the window to be dragged where the are no other controls present.
// PreviewMouseButton could be used, but then you have to do more work to ensure that if
// you're pressing down on a button, it executes its routine if dragging was not performed.
private void Window_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
// Set the dragging flag to true, so that position would not be reset automatically.
if (e.ChangedButton == System.Windows.Input.MouseButton.Left)
{
dragging = true;
this.DragMove();
}
}
// Similar to MouseDown. We're setting dragging flag to false to allow automatic
// positioning.
private void Window_MouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (e.ChangedButton == System.Windows.Input.MouseButton.Left)
{
dragging = false;
}
}
// Sets the initial position of the window. I made mine static for now, but later it can
// be modified to be whatever the user chooses in the settings.
private void SetInitialWindowPosition()
{
this.Left = screen.Width - this.Width;
this.Top = screen.Height / 2 - this.Height / 2;
}
// Setup the monitoring routine that automatically positions the window based on its location
// relative to the working area.
private void InitializeWindowPositionMonitoring()
{
var timer = new System.Windows.Threading.DispatcherTimer();
timer.Tick += delegate
{
// Check if window is being dragged (assuming that mouse down on any portion of the
// window is connected to dragging). This is a fairly safe assumption and held
// true thus far. Even if you're not performing any dragging, then the position
// doesn't change and nothing gets reset. You can add an extra check to see if
// position has changed, but there is no significant performance gain.
// Correct me if I'm wrong, but this is just O(n) execution, where n is the number of
// ticks the mouse has been held down on that window.
if (!dragging)
{
// Checking the left side of the window.
if (this.Left > screen.Width - this.Width)
{
this.Left = screen.Width - this.Width;
}
else if (this.Left < 0)
{
this.Left = 0;
}
// Checking the top of the window.
if (this.Top > screen.Height - this.Height)
{
this.Top = screen.Height - this.Height;
}
else if (this.Top < 0)
{
this.Top = 0;
}
}
};
// Adjust this based on performance and preference. I set mine to 10 milliseconds.
timer.Interval = new TimeSpan(0, 0, 0, 0, 10);
timer.Start();
}
}
Make sure that your window has the following:
MouseDown="Window_MouseDown"
MouseUp="Window_MouseUp"
WindowStartupLocation="Manual"
Loaded="Window_Loaded"
Also, this doesn't work well with Windows native components of the window, such as the top bar, so I disable the style and create my own (which is actually good for me, since I don't want the windows style for this):
WindowStyle="None"
I don't like the polling approach, but it's been excessively difficult to find a better solution that is still simple in WPF, so I'm gonna post my own.
The solution that I found is actually quite simple, in that it reimplements the behaviour of the DragMove() method of the window, which gives you the option to change the window position while it's being dragged. The following code reimplements DragMove() by storing the distance between the top left corner of the window and the mouse cursor.
public partial class MainWindow : Window
{
// this is the offset of the mouse cursor from the top left corner of the window
private Point offset = new Point();
public MainWindow()
{
InitializeComponent();
}
private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Point cursorPos = PointToScreen(Mouse.GetPosition(this));
Point windowPos = new Point(this.Left, this.Top);
offset = (Point)(cursorPos - windowPos);
// capturing the mouse here will redirect all events to this window, even if
// the mouse cursor should leave the window area
Mouse.Capture(this, CaptureMode.Element);
}
private void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
Mouse.Capture(null);
}
private void OnMouseMove(object sender, MouseEventArgs e)
{
if (Mouse.Captured == this && Mouse.LeftButton == MouseButtonState.Pressed)
{
Point cursorPos = PointToScreen(Mouse.GetPosition(this));
double newLeft = cursorPos.X - offset.X;
double newTop = cursorPos.Y - offset.Y;
// here you can change the window position and implement
// the snapping behaviour that you need
this.Left = newLeft;
this.Top = newTop;
}
}
}
Now you could implement the snapping / sticky window behaviour like this: The window will stick to the edge of the screen if it's within a range of 25 pixels (plus or minus).
int snappingMargin = 25;
if (Math.Abs(SystemParameters.WorkArea.Left - newLeft) < snappingMargin)
newLeft = SystemParameters.WorkArea.Left;
else if (Math.Abs(newLeft + this.ActualWidth - SystemParameters.WorkArea.Left - SystemParameters.WorkArea.Width) < snappingMargin)
newLeft = SystemParameters.WorkArea.Left + SystemParameters.WorkArea.Width - this.ActualWidth;
if (Math.Abs(SystemParameters.WorkArea.Top - newTop) < snappingMargin)
newTop = SystemParameters.WorkArea.Top;
else if (Math.Abs(newTop + this.ActualHeight - SystemParameters.WorkArea.Top - SystemParameters.WorkArea.Height) < snappingMargin)
newTop = SystemParameters.WorkArea.Top + SystemParameters.WorkArea.Height - this.ActualHeight;
The downside of this approach is that the snapping will not work, if the window is being dragged on the title bar, because that doesn't fire the OnMouseLeftButtonDown event (which I don't need, because my window is borderless). Maybe it will still help someone.
There is no API calls you can make (as far as I've seen) to use the Windows snapping features, however you could just get the System.Windows.Forms.Screen.PrimaryScreen.WorkingArea of the screen and set your Top, Left, Height and Width Properties of your Window accordingly.
Edit: The above suggestion does require Forms, which you probably don't want. I believe the WPF equivalent is System.Windows.SystemParameters.WorkArea