I have a Winforms application that the user uses to take a region based screenshot. I want to have a small preview pane, but i'm not sure how to do it. So far i tried recreating a bitmap on mouse move and its just way too laggy to be usable. So i thought if i used a pre-defined image (screenshot of the whole screen) and moved it inside the picturebox based off the mouse location so that you get a zoomed in look at the screen (to select the precise pixels you want to take the screenshot with easier). I'm not sure how i can implement this, i am also pretty new to drawing so i will show you what i have now.
private void falseDesktop_MouseMove(object sender, MouseEventArgs e)
{
zoomBox.Image = showZoomBox(e.Location);
zoomBox.Invalidate();
}
private Image showZoomBox(Point curLocation)
{
int x = 0;
int y = 0;
if (curLocation.X - 12 <= 0)
{
x = curLocation.X - 12;
}
else
{
x = curLocation.X;
}
if (curLocation.Y - 11 <= 0)
{
y = curLocation.Y - 11;
}
else
{
y = curLocation.Y;
}
Point start = new Point(curLocation.X - 12, curLocation.Y - 11);
Size size = new Size(24, 22);
Rectangle rect = new Rectangle(start, size);
Image selection = cropImage(falseDesktop.Image, rect);
return selection;
}
private static Image cropImage(Image img, Rectangle cropArea)
{
if (cropArea.Width != 0 && cropArea.Height != 0)
{
Bitmap bmpImage = new Bitmap(img);
Bitmap bmpCrop = bmpImage.Clone(cropArea, bmpImage.PixelFormat);
bmpImage.Dispose();
return (Image)(bmpCrop);
}
return null;
}
EDIT:
Here is a mock up like requested:
The black part of this image is a panel, of course the text being a label and the area where you see the image (stack overflow) would be the picturebox (called zoomBox) the lines on top of the zoomBox would be a guide and where the 2 lines intersect would be the mouse position. Hope this is a better assist to help you understand my issue.
Another thing that might help explain my issue is The form actually fills the entire screen with a "false desktop". its a picturebox that covers the entire screen with a screenshot of the desktop when "printscreen" was pressed. So I want this little "preview pane" to basically be a zoomed in location of where the mouse is.
This is a little bit laggy, but worth a try:
It's a WInForms app in a single file showing how a "live" zoom might work. It doesn't paint the cross hairs etc. that's up to you.
Key Parts:
_scale
PictureBoxSizeMode.StretchImage
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Drawing.Imaging;
static class Program {
[STAThread]
static void Main() {
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
public class Form1 : Form {
private Bitmap _myImage = new Bitmap(#"C:\Users\Public\Pictures\Sample Pictures\LightHouse.jpg");
private int _scale = 10; // keep this < 15
private PictureBox pboxMain;
private PictureBox pboxZoom;
private System.ComponentModel.IContainer components;
public Form1() {
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e) {
pboxMain.Image = _myImage;
}
private void pboxMain_MouseMove(object sender, MouseEventArgs e) {
try {
Rectangle rc = new Rectangle(
new Point(e.X - _scale, e.Y - _scale),
new Size(_scale * 2, _scale * 2));
pboxZoom.Image = _myImage.Clone(rc, PixelFormat.DontCare);
}
catch (OutOfMemoryException ex) {/* ignore... */}
}
protected override void Dispose(bool disposing) {
if (disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}
private void InitializeComponent() {
this.pboxMain = new PictureBox();
this.pboxZoom = new PictureBox();
((System.ComponentModel.ISupportInitialize)(this.pboxMain)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.pboxZoom)).BeginInit();
this.SuspendLayout();
this.pboxMain.Dock = DockStyle.Fill;
this.pboxMain.Location = new System.Drawing.Point(0, 0);
this.pboxMain.Name = "pboxMain";
this.pboxMain.Size = new System.Drawing.Size(767, 435);
this.pboxMain.TabIndex = 0;
this.pboxMain.TabStop = false;
this.pboxMain.MouseMove += new MouseEventHandler(this.pboxMain_MouseMove);
this.pboxZoom.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(255)))),
((int)(((byte)(255)))), ((int)(((byte)(192)))));
this.pboxZoom.BorderStyle = BorderStyle.FixedSingle;
this.pboxZoom.Location = new System.Drawing.Point(12, 12);
this.pboxZoom.Name = "pboxZoom";
this.pboxZoom.Size = new System.Drawing.Size(106, 83);
this.pboxZoom.SizeMode = PictureBoxSizeMode.StretchImage;
this.pboxZoom.TabIndex = 1;
this.pboxZoom.TabStop = false;
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(767, 435);
this.Controls.Add(this.pboxZoom);
this.Controls.Add(this.pboxMain);
this.Name = "Form1";
this.Text = "Form1";
this.Load += new System.EventHandler(this.Form1_Load);
((System.ComponentModel.ISupportInitialize)(this.pboxMain)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.pboxZoom)).EndInit();
this.ResumeLayout(false);
}
}
This should be of great help TeboScreen: Basic C# Screen Capture
Why reinvent the Wheel :-)
Related
Here is the following rectangle below:
When I resize the form, I need this rectangle to match the size of the form.
When changing the width of the rectangle, do not interfere with its visibility within the form.
I'm using the following:
Note:
I did the rectangle manually, but if you have rectangle ready, better yet!
public Form1()
{
InitializeComponent();
this.Paint += Form1_Paint;
this.rectangles = new Dictionary<string, Rectangle>();
this.sizeScreen = this.Size;
this.sizeRectangles = new Size(8, 8);
this.brush = new SolidBrush(Color.Red);
FillLeft();
FillRight();
FillUp();
FillDown();
}
private Size sizeScreen;
private Size sizeRectangles;
private SolidBrush brush;
private Dictionary<string, Rectangle> rectangles;
private void FillLeft()
{
Rectangle rectangle = new Rectangle()
{
Height = this.sizeScreen.Height,
Width = this.sizeRectangles.Width,
X = 0,
Y = this.sizeRectangles.Height
};
this.rectangles.Add("left", rectangle);
}
private void FillRight()
{
Rectangle rectangle = new Rectangle()
{
Height = this.sizeScreen.Height,
Width = this.sizeRectangles.Width,
X = this.sizeScreen.Width - (this.sizeRectangles.Width * 5),
Y = this.sizeRectangles.Height
};
this.rectangles.Add("right", rectangle);
}
private void FillUp()
{
Rectangle rectangle = new Rectangle()
{
Height = this.sizeRectangles.Height,
Width = this.sizeScreen.Width,
X = 0,
Y = this.sizeRectangles.Height
};
this.rectangles.Add("up", rectangle);
}
private void FillDown()
{
Rectangle rectangle = new Rectangle()
{
Height = this.sizeRectangles.Height,
Width = this.sizeScreen.Width,
X = 0,
Y = this.sizeScreen.Height - (this.sizeRectangles.Height * 11)
};
this.rectangles.Add("down", rectangle);
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
for (int i = 0; i < this.rectangles.Count; i++)
{
e.Graphics.FillRectangles(this.brush, this.rectangles.Values.ToArray());
}
}
I want to set the rectangle on the form when it is resized
This is the way I'm creating the rectangle, but it does not stay right on the screen, to resize it I do not know
I think this would simplify what you are trying to do:
const int PenWidth = 10;
private void Form1_Paint(object sender, PaintEventArgs e)
{
Rectangle r = this.ClientRectangle;
Pen pen = new Pen(Color.Red, PenWidth);
e.Graphics.DrawRectangle(pen, r);
}
You could even add a margin:
const int PenWidth = 10;
const int PenMargin = 10;
private void Form1_Paint(object sender, PaintEventArgs e)
{
Rectangle r = this.ClientRectangle;
r.Inflate(-PenMargin, -PenMargin);
Pen pen = new Pen(Color.Red, PenWidth);
e.Graphics.DrawRectangle(pen, r);
}
To prevent traces (suggested by Wyck):
private void Form1_Resize(object sender, EventArgs e)
{
Invalidate();
}
Handle the Resize event and call Invalidate in the handler. Create a Pen of the desired color and width and set its Alignment to Inset. Handle the Paint event and in the handler call DrawRectangle passing in the ClientRectangle of the form.
Here is an example.
const float borderWidth = 8.0f;
Pen borderPen = new Pen(Color.Red, borderWidth) { Alignment = System.Drawing.Drawing2D.PenAlignment.Inset };
public Form2()
{
InitializeComponent();
this.Paint += Form2_Paint;
this.Resize += Form2_Resize;
}
private void Form2_Resize(object sender, EventArgs e)
{
Invalidate();
}
private void Form2_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawRectangle(borderPen, this.ClientRectangle);
}
Apply the following fixes to the code:
Set ResizeRedraw property of the form to true. It sets the underlying style for the form so by each resize it sends the paint message and you don't need to handle Resize event.
Use DrawRectangle and draw using wide pen. So you don't need to fill multiple rectangles.
Set the PenAlignment to Inset. So you don't need to calculate the location of rectangle.
Do dispose the pen when you don't need it.
Example
public Form1()
{
InitializeComponent();
ResizeRedraw = true;
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
using (var pen = new Pen(Color.Red, PenWidth))
{
pen.Alignment = System.Drawing.Drawing2D.PenAlignment.Inset;
e.Graphics.DrawRectangle(pen, ClientRectangle);
}
}
i have used this on my current project. when ever you resize the form, it will automatically resize all the object inside your form
i have class named clsResize and i call this on the form load.
1st you have to initialize the class inside the form then create 2 new method.
see example below
public partial class frmNewForm : Form
{
clsResize _form_resize;
public string selectedProd;
public frmNewForm()
{
InitializeComponent();
_form_resize = new clsResize(this);
this.Load += _Load;
this.Resize += _Resize;
}
private void _Load(object sender, EventArgs e)
{
_form_resize._get_initial_size();
}
private void _Resize(object sender, EventArgs e)
{
_form_resize._resize();
}
}
and here is the class that i used.
public class clsResize
{
List<System.Drawing.Rectangle> _arr_control_storage = new List<System.Drawing.Rectangle>();
private bool showRowHeader = false;
public clsResize(Form _form_)
{
form = _form_; //the calling form
_formSize = _form_.ClientSize; //Save initial form size
_fontsize = _form_.Font.Size; //Font size
}
private float _fontsize { get; set; }
private System.Drawing.SizeF _formSize {get;set; }
private Form form { get; set; }
public void _get_initial_size() //get initial size//
{
var _controls = _get_all_controls(form);//call the enumerator
foreach (Control control in _controls) //Loop through the controls
{
_arr_control_storage.Add(control.Bounds); //saves control bounds/dimension
//If you have datagridview
if (control.GetType() == typeof(DataGridView))
_dgv_Column_Adjust(((DataGridView)control), showRowHeader);
}
}
public void _resize() //Set the resize
{
double _form_ratio_width = (double)form.ClientSize.Width /(double)_formSize.Width; //ratio could be greater or less than 1
double _form_ratio_height = (double)form.ClientSize.Height / (double)_formSize.Height; // this one too
var _controls = _get_all_controls(form); //reenumerate the control collection
int _pos = -1;//do not change this value unless you know what you are doing
try
{
foreach (Control control in _controls)
{
// do some math calc
_pos += 1;//increment by 1;
System.Drawing.Size _controlSize = new System.Drawing.Size((int)(_arr_control_storage[_pos].Width * _form_ratio_width),
(int)(_arr_control_storage[_pos].Height * _form_ratio_height)); //use for sizing
System.Drawing.Point _controlposition = new System.Drawing.Point((int)
(_arr_control_storage[_pos].X * _form_ratio_width), (int)(_arr_control_storage[_pos].Y * _form_ratio_height));//use for location
//set bounds
control.Bounds = new System.Drawing.Rectangle(_controlposition, _controlSize); //Put together
//Assuming you have a datagridview inside a form()
//if you want to show the row header, replace the false statement of
//showRowHeader on top/public declaration to true;
if (control.GetType() == typeof(DataGridView))
_dgv_Column_Adjust(((DataGridView)control), showRowHeader);
//Font AutoSize
control.Font = new System.Drawing.Font(form.Font.FontFamily,
(float)(((Convert.ToDouble(_fontsize) * _form_ratio_width) / 2) +
((Convert.ToDouble(_fontsize) * _form_ratio_height) / 2)));
}
}
catch(Exception e)
{
MessageBox.Show(e.Message);
return;
}
}
private void _dgv_Column_Adjust(DataGridView dgv, bool _showRowHeader) //if you have Datagridview
//and want to resize the column base on its dimension.
{
int intRowHeader = 0;
const int Hscrollbarwidth = 5;
if (_showRowHeader)
intRowHeader = dgv.RowHeadersWidth;
else
dgv.RowHeadersVisible = false;
for (int i = 0; i < dgv.ColumnCount; i++)
{
if (dgv.Dock == DockStyle.Fill) //in case the datagridview is docked
dgv.Columns[i].Width = ((dgv.Width - intRowHeader) / dgv.ColumnCount);
else
dgv.Columns[i].Width = ((dgv.Width - intRowHeader - Hscrollbarwidth) / dgv.ColumnCount);
}
}
private static IEnumerable<Control> _get_all_controls(Control c)
{
return c.Controls.Cast<Control>().SelectMany(item =>
_get_all_controls(item)).Concat(c.Controls.Cast<Control>()).Where(control =>
control.Name != string.Empty);
}
}
So I am trying to create a smart crosshair controlled by win 10 voice recognition engine for a game.
The form 1 is used to turn on and turn off voice recognition .If it hears my saying "slow", it will draw a line on another transparent overlaying form
public partial class Form1 : Form
{
formoverlay overlayform = new formoverlay();
SpeechRecognitionEngine dasSpeechEngine = new SpeechRecognitionEngine();
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
dasSpeechEngine.RecognizeAsync(RecognizeMode.Multiple);
btnDisable.Enabled = true;
}
private void Form1_Load(object sender, EventArgs e)//load event
{
Choices commands = new Choices();
commands.Add(new string[] { "slow", "medium", "fast","ultra","x"});
GrammarBuilder gbuilder = new GrammarBuilder();
gbuilder.Append(commands);
Grammar thegrammar = new Grammar(gbuilder);
dasSpeechEngine.LoadGrammarAsync(thegrammar);
dasSpeechEngine.SetInputToDefaultAudioDevice();
dasSpeechEngine.SpeechRecognized += dasSpeechEngine_SpeechRecognized;
overlayform.timestart = DateTime.Now.Second;
//MessageBox.Show(overlayform.timestart.ToString());
//for (si=1,si)
}
private void dasSpeechEngine_SpeechRecognized(object sender, SpeechRecognizedEventArgs e)
{
switch (e.Result.Text)
{
case "slow":
if (ModifierKeys.HasFlag(Keys.Control))//prevent accidentally sight change
{
//clear current canvas
//draw all the lines that has true bool flag
//draw all the marks
overlayform.theclearform();
overlayform.paintHorline(15, slowAimSet.lineStartY,slowAimSet.lineEndY,1);
break;
}
break;
}
}
}
On the overlay form, the code (not including the partial class declaration):
Graphics g;
Pen myPen = new Pen(Color.Green);
Pen smallPen = new Pen(Color.GreenYellow);
public formoverlay()
{
InitializeComponent();
}
private void formoverlay_Load(object sender, EventArgs e)
{
this.BackColor = Color.Wheat;
this.TransparencyKey = Color.Wheat;
this.TopMost = true;
this.FormBorderStyle = FormBorderStyle.None;
int initialStyle = GetWindowLong (this.Handle, -20);
SetWindowLong(this.Handle, -20, initialStyle | 0x80000 | 0x20);
//enable click through
//get wows window size;
GetWindowRect(handle, out rect);
this.Size = new Size(rect.right - rect.left, rect.bottom - rect.top);
this.Top = rect.top;
this.Left = rect.left;
smallPen.Width = 2.0f;
}
public void paintHorline(int theTgtSpd,int tStartY, int tEndY, int thisRngMarkerDir)
{
int rngMarkerOffset = 10;
//aimlineForSpeed[] thisAimLine = new aimlineForSpeed [4];
//g.DrawLine(myPen, 500,500, 600, 600);
g.DrawLine(myPen, 0,tStartY, 1920, tEndY);
}
public void theclearform()//everytime user say, re-draw everything
{
Invalidate();
}
When I run the program, when I say "slow", there us an errorenter image description here
saying system argument exception: {"Parameter is not valid."}
It does not make any difference if I write constant values in my draw calls, for example, g.DrawRectangle(myPen, 100, 100, 200,200);
Apparently, the program calls "painthorline" function, but it does not draw correctly.
If I create a new graphics in painthorline method, it won't cause this error, but it does not draw anything at all..
Where did I go wrong?
Did I need to create a sperate event in order to get the drawline/drawrectangle call works?
My Program: Contains a form with few textboxes and one button. 'Default Printer' is set as Adobe PDF on my computer.
My Goal: Want to take a screenshot of a form/usercontrol when the user clicks 'Print' button. The screenshot is then saved on the desktop in .pdf format.
My Problem: I have following two problems with the code:
Size of Screenshot: The size of the screenshot is too big and it does not fit the size of the page (default page size) when printed/converted to .pdf. Please refer the two images below. I want the entire screenshot to fit inside the page.
Asks twice where to convert and save: When I click on 'Print Form' button, programs asks me TWICE where to print/convert and save the file. I want the program to ask me only Once, where to print and save the file.
Problem 1: The screenshot captured by the program does not fit the page when printed.
I want the screenshot image to fit like this on one page of .pdf:
Code:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
button1.Text = "Print Form";
button1.Click += new EventHandler(button1_Click);
printDocument1.PrintPage += new PrintPageEventHandler(printDocument1_PrintPage);
this.Controls.Add(button1);
}
private void button1_Click(object sender, EventArgs e)
{
CaptureScreen();
printDocument1.Print();
}
Bitmap memoryImage;
private void CaptureScreen()
{
Graphics myGraphics = this.CreateGraphics();
Size s = this.Size;
memoryImage = new Bitmap(s.Width, s.Height, myGraphics);
Graphics memoryGraphics = Graphics.FromImage(memoryImage);
memoryGraphics.CopyFromScreen(this.Location.X, this.Location.Y, 0, 0, s);
}
private void printDocument1_PrintPage(System.Object sender, System.Drawing.Printing.PrintPageEventArgs e)
{
e.Graphics.DrawImage(memoryImage, 0, 0);
}
}
Thanks for your help in advance. I am a newbie, learning c# language and your help will be much appreciated. :)
Ok, check this out, and the modified printDocument1_PrintPage in particular:
private void printDocument1_PrintPage(object sender, PrintPageEventArgs e)
{
// calculate width and height scalings taking page margins into account
var wScale = e.MarginBounds.Width / (float)_memoryImage.Width;
var hScale = e.MarginBounds.Height / (float)_memoryImage.Height;
// choose the smaller of the two scales
var scale = wScale < hScale ? wScale : hScale;
// apply scaling to the image
e.Graphics.ScaleTransform(scale, scale);
// print to default printer's page
e.Graphics.DrawImage(_memoryImage, 0, 0);
}
I moved all the event wireup into InitializeComponent where it's usually supposed to go but it's more involved code:
using System;
using System.Drawing;
using System.Drawing.Printing;
using System.Windows.Forms;
namespace testScreenCapScale
{
public partial class Form1 : Form
{
public Form1() { InitializeComponent(); }
private void button1_Click(object sender, EventArgs e)
{
CaptureScreen();
printDocument1.Print();
}
private Bitmap _memoryImage;
private void CaptureScreen()
{
// put into using construct because Graphics objects do not
// get automatically disposed when leaving method scope
using (var myGraphics = CreateGraphics())
{
var s = Size;
_memoryImage = new Bitmap(s.Width, s.Height, myGraphics);
using (var memoryGraphics = Graphics.FromImage(_memoryImage))
{
memoryGraphics.CopyFromScreen(Location.X, Location.Y, 0, 0, s);
}
}
}
private void printDocument1_PrintPage(object sender, PrintPageEventArgs e)
{
// calculate width and height scalings taking page margins into account
var wScale = e.MarginBounds.Width / (float)_memoryImage.Width;
var hScale = e.MarginBounds.Height / (float)_memoryImage.Height;
// choose the smaller of the two scales
var scale = wScale < hScale ? wScale : hScale;
// apply scaling to the image
e.Graphics.ScaleTransform(scale, scale);
// print to default printer's page
e.Graphics.DrawImage(_memoryImage, 0, 0);
}
}
}
Form1.Designer.cs
namespace testScreenCapScale
{
partial class Form1
{
private System.ComponentModel.IContainer components = null;
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
components.Dispose();
base.Dispose(disposing);
}
private void InitializeComponent()
{
this.printDocument1 = new System.Drawing.Printing.PrintDocument();
this.button1 = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// printDocument1
//
this.printDocument1.PrintPage += new System.Drawing.Printing.PrintPageEventHandler(this.printDocument1_PrintPage);
//
// button1
//
this.button1.Location = new System.Drawing.Point(64, 220);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(75, 23);
this.button1.TabIndex = 0;
this.button1.Text = "button1";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(384, 377);
this.Controls.Add(this.button1);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);
}
private System.Drawing.Printing.PrintDocument printDocument1;
private System.Windows.Forms.Button button1;
}
}
I'm looking for the fastest way to refresh an image (and draw some shapes) on a Winform. I'm using a PictureBox now, but I'm open for suggestions.
The way I'm getting the max FPS possible I took from here: "My last post on render loops (hopefully)"
This is a game loop pattern, but it can also be used to other purpose, like to display live images acquired from a camera.
I'm drawing a red circle in different positions.
As you can see, the FPS drops a lot when a Bitmap is loaded at the PictureBox. Especially in the SizeMode Zoom (the one I want to use).
My PC values for FPS:
Form size (start size)
Bitmap ON: 140 (Zoom); 1000 (Normal); 135 (Stretch); 880 (Auto); 1000 (Center)
Bitmap OFF: 3400 !!
Form size (after Zoom 100% click)
Bitmap ON: 40 (Zoom); 150 (Normal); 65 (Stretch); 150 (Auto); 150 (Center)
Bitmap OFF: 540 !!
Edit 1:
Well, this results was for a 1024x1024 image. But I realizy that the form Height was not getting the right value, I found out that it is because the form size is limited by the Screen resolution. So I've edited the code to load a 800x600 Bitmap. Now the Zoom 100% button works. As you can see, at zoom 100% the FPS is 350, but if you increase the size of the PictureBox one more pixel, the FPS drops to 35, about 10x slower ¬¬ !
The big question is: How can I improve the FPS, specially in zoom mode?
PS: I know WinForm is not the best way to go, I also know WPF. But for now I'm looking the solution using WinForm. Ok? The reason is that I have a big scary project using it. I'm planing to move to WPF, but I'm still learning it. :)
Here is the full code, so you can test it by yourself.
Form1.cs:
//.NET 4.0 Client Profile
using System;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Windows.Forms;
//You need to add System.Activities.Presentation.dll
using System.Activities.Presentation.Hosting;
namespace WindowsFormsApplication2
{
public enum DrawSource
{
None = 0,
PictureBox = 1,
Grapghics = 2,
}
public partial class Form1 : Form
{
private readonly Timer _updateFPSTimer;
private bool _isIdle;
private Point _location;
private double _updateCount;
private readonly Bitmap _backImage;
public Form1()
{
InitializeComponent();
this.DoubleBuffered = this.ckbDoubleBufferForm.Checked;
this.pictureBox1.DoubleBuffered = this.ckbDoubleBufferPictureBox.Checked;
_backImage = new Bitmap(800, 600, PixelFormat.Format24bppRgb);
Graphics g = Graphics.FromImage(_backImage);
int penWidth = 10;
Pen pen = new Pen(Brushes.Blue, penWidth);
g.DrawRectangle(pen, new Rectangle(new Point(penWidth, penWidth), new Size(_backImage.Size.Width - 2 * penWidth, _backImage.Size.Height - 2 * penWidth)));
this.cbxSizeMode.DataSource = Enum.GetValues(typeof(PictureBoxSizeMode));
this.cbxSizeMode.SelectedItem = PictureBoxSizeMode.Zoom;
this.cbxBitmapDrawSource.DataSource = Enum.GetValues(typeof(DrawSource));
this.cbxBitmapDrawSource.SelectedItem = DrawSource.PictureBox;
this.pictureBox1.SizeMode = PictureBoxSizeMode.Zoom;
Application.Idle += Application_Idle;
_updateFPSTimer = new Timer();
_updateFPSTimer.Tick += UpdateFPSTimer_Tick;
_updateFPSTimer.Interval = Convert.ToInt32(1000.0 / 5); //Period in s = 1 s / 5 Hz
_updateFPSTimer.Start();
}
private void Application_Idle(object sender, EventArgs e)
{
while (this.ckbRun.Checked && IsAppStillIdle())
this.pictureBox1.Refresh();
}
void UpdateFPSTimer_Tick(object sender, EventArgs e)
{
GetFPS();
}
private void GetFPS()
{
double fps = _updateCount / (Convert.ToDouble(_updateFPSTimer.Interval) / 1000.0);
_updateCount = 0;
lblFps.Text = fps.ToString("0.00");
}
private void PictureBox1_Paint(object sender, PaintEventArgs e)
{
DrawCircle(e.Graphics);
}
private void DrawCircle(Graphics g)
{
if (this.pictureBox1.Image == null && ((DrawSource)this.cbxBitmapDrawSource.SelectedItem == DrawSource.Grapghics))
g.DrawImage(_backImage, 0, 0, g.ClipBounds.Width, g.ClipBounds.Height);
var rect = new Rectangle(_location, new Size(30, 30));
g.DrawEllipse(Pens.Red, rect);
_location.X += 1;
_location.Y += 1;
if (_location.X > g.ClipBounds.Width)
_location.X = 0;
if (_location.Y > g.ClipBounds.Height)
_location.Y = 0;
_updateCount++;
}
/// <summary>
/// Gets if the app still idle.
/// http://blogs.msdn.com/b/tmiller/archive/2005/05/05/415008.aspx
/// </summary>
/// <returns></returns>
private bool IsAppStillIdle()
{
Message msg;
return !PeekMessage(out msg, IntPtr.Zero, 0, 0, 0);
}
#region Unmanaged Get PeekMessage
// http://blogs.msdn.com/b/tmiller/archive/2005/05/05/415008.aspx
[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern bool PeekMessage(out Message msg, IntPtr hWnd, uint messageFilterMin, uint messageFilterMax, uint flags);
#endregion
private void btnZoomReset_Click(object sender, EventArgs e)
{
if (_backImage != null)
{
//Any smarter way to do this?
//Note that the maximum Form size is limmited by designe by the Screen resolution.
//Rectangle screenRectangle = RectangleToScreen(this.ClientRectangle);
//int titleHeight = screenRectangle.Top - this.Top;
//Borders
Size border = new Size();
border.Width = this.Width - this.pictureBox1.Width;
border.Height = this.Height - this.pictureBox1.Height;
this.Width = border.Width + _backImage.Width;
this.Height = border.Height + _backImage.Height;
Console.WriteLine("PictureBox size: " + this.pictureBox1.Size.ToString());
}
}
private void SizeMode_SelectedIndexChanged(object sender, EventArgs e)
{
PictureBoxSizeMode mode;
Enum.TryParse<PictureBoxSizeMode>(cbxSizeMode.SelectedValue.ToString(), out mode);
this.pictureBox1.SizeMode = mode;
}
private void DoubleBufferForm_CheckedChanged(object sender, EventArgs e)
{
this.DoubleBuffered = this.ckbDoubleBufferForm.Checked;
}
private void DoubleBufferPictureBox_CheckedChanged(object sender, EventArgs e)
{
this.pictureBox1.DoubleBuffered = this.ckbDoubleBufferPictureBox.Checked;
}
private void BitmapDrawSource_SelectedIndexChanged(object sender, EventArgs e)
{
if ((DrawSource)this.cbxBitmapDrawSource.SelectedItem == DrawSource.PictureBox)
this.pictureBox1.Image = _backImage;
else
this.pictureBox1.Image = null;
}
}
}
Designer.CS:
namespace WindowsFormsApplication2
{
partial class Form1
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.pictureBox1 = new WindowsFormsApplication2.PictureBoxDoubleBuffer();
this.label1 = new System.Windows.Forms.Label();
this.lblFps = new System.Windows.Forms.Label();
this.ckbRun = new System.Windows.Forms.CheckBox();
this.btnZoomReset = new System.Windows.Forms.Button();
this.label2 = new System.Windows.Forms.Label();
this.cbxSizeMode = new System.Windows.Forms.ComboBox();
this.gpbDoubleBuffer = new System.Windows.Forms.GroupBox();
this.ckbDoubleBufferPictureBox = new System.Windows.Forms.CheckBox();
this.ckbDoubleBufferForm = new System.Windows.Forms.CheckBox();
this.cbxBitmapDrawSource = new System.Windows.Forms.ComboBox();
this.label3 = new System.Windows.Forms.Label();
((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit();
this.gpbDoubleBuffer.SuspendLayout();
this.SuspendLayout();
//
// pictureBox1
//
this.pictureBox1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.pictureBox1.BackColor = System.Drawing.SystemColors.ControlDarkDark;
this.pictureBox1.DoubleBuffered = true;
this.pictureBox1.Location = new System.Drawing.Point(8, 84);
this.pictureBox1.Name = "pictureBox1";
this.pictureBox1.Size = new System.Drawing.Size(347, 215);
this.pictureBox1.TabIndex = 0;
this.pictureBox1.TabStop = false;
this.pictureBox1.Paint += new System.Windows.Forms.PaintEventHandler(this.PictureBox1_Paint);
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(134, 13);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(30, 13);
this.label1.TabIndex = 3;
this.label1.Text = "FPS:";
//
// lblFps
//
this.lblFps.AutoSize = true;
this.lblFps.Location = new System.Drawing.Point(160, 13);
this.lblFps.Name = "lblFps";
this.lblFps.Size = new System.Drawing.Size(10, 13);
this.lblFps.TabIndex = 4;
this.lblFps.Text = "-";
//
// ckbRun
//
this.ckbRun.Appearance = System.Windows.Forms.Appearance.Button;
this.ckbRun.AutoSize = true;
this.ckbRun.Checked = true;
this.ckbRun.CheckState = System.Windows.Forms.CheckState.Checked;
this.ckbRun.Location = new System.Drawing.Point(8, 8);
this.ckbRun.Name = "ckbRun";
this.ckbRun.Size = new System.Drawing.Size(37, 23);
this.ckbRun.TabIndex = 5;
this.ckbRun.Text = "Run";
this.ckbRun.UseVisualStyleBackColor = true;
//
// btnZoomReset
//
this.btnZoomReset.Location = new System.Drawing.Point(51, 8);
this.btnZoomReset.Name = "btnZoomReset";
this.btnZoomReset.Size = new System.Drawing.Size(74, 23);
this.btnZoomReset.TabIndex = 7;
this.btnZoomReset.Text = "Zoom 100%";
this.btnZoomReset.UseVisualStyleBackColor = true;
this.btnZoomReset.Click += new System.EventHandler(this.btnZoomReset_Click);
//
// label2
//
this.label2.AutoSize = true;
this.label2.Location = new System.Drawing.Point(5, 37);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(57, 13);
this.label2.TabIndex = 8;
this.label2.Text = "SizeMode:";
//
// cbxSizeMode
//
this.cbxSizeMode.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.cbxSizeMode.FormattingEnabled = true;
this.cbxSizeMode.Location = new System.Drawing.Point(73, 34);
this.cbxSizeMode.Name = "cbxSizeMode";
this.cbxSizeMode.Size = new System.Drawing.Size(84, 21);
this.cbxSizeMode.TabIndex = 9;
this.cbxSizeMode.SelectedIndexChanged += new System.EventHandler(this.SizeMode_SelectedIndexChanged);
//
// gpbDoubleBuffer
//
this.gpbDoubleBuffer.Controls.Add(this.ckbDoubleBufferPictureBox);
this.gpbDoubleBuffer.Controls.Add(this.ckbDoubleBufferForm);
this.gpbDoubleBuffer.Location = new System.Drawing.Point(221, 8);
this.gpbDoubleBuffer.Name = "gpbDoubleBuffer";
this.gpbDoubleBuffer.Size = new System.Drawing.Size(99, 65);
this.gpbDoubleBuffer.TabIndex = 10;
this.gpbDoubleBuffer.TabStop = false;
this.gpbDoubleBuffer.Text = "Double Buffer";
//
// ckbDoubleBufferPictureBox
//
this.ckbDoubleBufferPictureBox.AutoSize = true;
this.ckbDoubleBufferPictureBox.Checked = true;
this.ckbDoubleBufferPictureBox.CheckState = System.Windows.Forms.CheckState.Checked;
this.ckbDoubleBufferPictureBox.Location = new System.Drawing.Point(9, 38);
this.ckbDoubleBufferPictureBox.Name = "ckbDoubleBufferPictureBox";
this.ckbDoubleBufferPictureBox.Size = new System.Drawing.Size(77, 17);
this.ckbDoubleBufferPictureBox.TabIndex = 8;
this.ckbDoubleBufferPictureBox.Text = "PictureBox";
this.ckbDoubleBufferPictureBox.UseVisualStyleBackColor = true;
this.ckbDoubleBufferPictureBox.CheckedChanged += new System.EventHandler(this.DoubleBufferPictureBox_CheckedChanged);
//
// ckbDoubleBufferForm
//
this.ckbDoubleBufferForm.AutoSize = true;
this.ckbDoubleBufferForm.Checked = true;
this.ckbDoubleBufferForm.CheckState = System.Windows.Forms.CheckState.Checked;
this.ckbDoubleBufferForm.Location = new System.Drawing.Point(9, 15);
this.ckbDoubleBufferForm.Name = "ckbDoubleBufferForm";
this.ckbDoubleBufferForm.Size = new System.Drawing.Size(49, 17);
this.ckbDoubleBufferForm.TabIndex = 7;
this.ckbDoubleBufferForm.Text = "Form";
this.ckbDoubleBufferForm.UseVisualStyleBackColor = true;
this.ckbDoubleBufferForm.CheckedChanged += new System.EventHandler(this.DoubleBufferForm_CheckedChanged);
//
// cbxBitmapDrawSource
//
this.cbxBitmapDrawSource.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.cbxBitmapDrawSource.FormattingEnabled = true;
this.cbxBitmapDrawSource.Items.AddRange(new object[] {
"PictureBox",
"Graphics"});
this.cbxBitmapDrawSource.Location = new System.Drawing.Point(73, 57);
this.cbxBitmapDrawSource.Name = "cbxBitmapDrawSource";
this.cbxBitmapDrawSource.Size = new System.Drawing.Size(84, 21);
this.cbxBitmapDrawSource.TabIndex = 12;
this.cbxBitmapDrawSource.SelectedIndexChanged += new System.EventHandler(this.BitmapDrawSource_SelectedIndexChanged);
//
// label3
//
this.label3.AutoSize = true;
this.label3.Location = new System.Drawing.Point(5, 60);
this.label3.Name = "label3";
this.label3.Size = new System.Drawing.Size(68, 13);
this.label3.TabIndex = 11;
this.label3.Text = "Bitmap draw:";
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(361, 304);
this.Controls.Add(this.cbxBitmapDrawSource);
this.Controls.Add(this.label3);
this.Controls.Add(this.gpbDoubleBuffer);
this.Controls.Add(this.cbxSizeMode);
this.Controls.Add(this.label2);
this.Controls.Add(this.btnZoomReset);
this.Controls.Add(this.ckbRun);
this.Controls.Add(this.lblFps);
this.Controls.Add(this.label1);
this.Controls.Add(this.pictureBox1);
this.DoubleBuffered = true;
this.Name = "Form1";
this.Text = "Max FPS tester";
((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit();
this.gpbDoubleBuffer.ResumeLayout(false);
this.gpbDoubleBuffer.PerformLayout();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
PictureBoxDoubleBuffer pictureBox1;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Label lblFps;
private System.Windows.Forms.CheckBox ckbRun;
private System.Windows.Forms.Button btnZoomReset;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.ComboBox cbxSizeMode;
private System.Windows.Forms.GroupBox gpbDoubleBuffer;
private System.Windows.Forms.CheckBox ckbDoubleBufferPictureBox;
private System.Windows.Forms.CheckBox ckbDoubleBufferForm;
private System.Windows.Forms.ComboBox cbxBitmapDrawSource;
private System.Windows.Forms.Label label3;
}
}
PictureBoxDoubleBuffer.cs:
using System.Windows.Forms;
namespace WindowsFormsApplication2
{
public class PictureBoxDoubleBuffer : PictureBox
{
public bool DoubleBuffered
{
get { return base.DoubleBuffered; }
set { base.DoubleBuffered = value; }
}
public PictureBoxDoubleBuffer()
{
base.DoubleBuffered = true;
}
}
}
"The big question is: How can I improve the FPS, specially in zoom mode?"
In Zoom (and Stretch) mode the Image() is probably being resized for each Refresh()...a very expensive operation. You could roll your own "zoom" mode that creates a new image that is already zoomed and assign that instead, leaving the mode at "Normal".
Here's a quick example using a cheat zoom with another PictureBox (obviously you could do the zoom mathematically by computing aspect ratio, etc...but that hurts my brain in the morning):
private void cbxSizeMode_SelectedIndexChanged(object sender, EventArgs e)
{
PictureBoxSizeMode mode;
Enum.TryParse<PictureBoxSizeMode>(cbxSizeMode.SelectedValue.ToString(), out mode);
this.pictureBox1.SizeMode = mode;
if (this.pictureBox1.SizeMode == PictureBoxSizeMode.Zoom)
{
using (PictureBox pb = new PictureBox())
{
pb.Size = pictureBox1.Size;
pb.SizeMode = PictureBoxSizeMode.Zoom;
pb.Image = _backImage;
Bitmap bmp = new Bitmap(pb.Size.Width, pb.Size.Height);
pb.DrawToBitmap(bmp, pb.ClientRectangle);
this.pictureBox1.SizeMode = PictureBoxSizeMode.Normal;
this.pictureBox1.Image = bmp;
}
}
else
{
pictureBox1.Image = _backImage;
}
}
You'll need to call this method when the form first runs if Zoom is the default selection, otherwise it won't have custom zoom image.
Now your Zoom mode should zoom along! (sorry, couldn't resist doing that)
I currently have a Windows Form project where I have created a simple chat application. Currently the chat is output to a multi line text box, however I want to now enhance it a little and add some styling. In time I wish to have some image, format it nicely and perhaps some HTML (although this isnt vital) in the future. I am just wondering what I should use to achieve this. I did think of updating a HTML page and then reload it with each new message, but this wouldn't give a very good user experience. I have also looked at the richtextbox class but this seems to be a little limited for what I am after. I am hoping some one can point me in the right direction as to what to use.
I am trying to achieve something similar as what I have highlighted in red:
While some of the other comments indicate that WPF is well suited to this, in the real world, it is not always possible or desirable to switch.
A regular owner-drawn list box is well suited to this purpose.
To create one, simply set the DrawMode on the list box to OwnerDrawVariable, e.g.
list.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawVariable;
Then you just need to provide two event handlers, the first to measure the item (tell the list box how tall the item will be), and another to actually render it. e.g.
this.list.DrawItem += new System.Windows.Forms.DrawItemEventHandler(this.list_DrawItem);
this.list.MeasureItem += new System.Windows.Forms.MeasureItemEventHandler(this.list_MeasureItem);
Rendering an image into the list is fairly simple with GDI+ DrawImage (where g is your graphics context_:
Bitmap bmp = Bitmap.FromFile("test.jpg");
Rectangle source = new Rectangle(0, 0, bmp.Width, bmp.Height);
Rectangle dest = source;
g.DrawImage(bmp, dest, source, GraphicsUnit.Pixel);
This is a sample Windows Form which has an owner-drawn list box of all fonts on the system, producing variable-height owner-drawn list items:
using System;
using System.Drawing;
using System.Windows.Forms;
namespace Font_Display
{
public class Test : System.Windows.Forms.Form
{
private Font head;
private System.Windows.Forms.ListBox list;
private System.ComponentModel.Container components = null;
public Test()
{
InitializeComponent();
head = new Font("Arial", 10, GraphicsUnit.Pixel);
}
protected override void Dispose(bool disposing)
{
if (disposing) {
if (components != null) {
components.Dispose();
}
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.list = new System.Windows.Forms.ListBox();
this.SuspendLayout();
//
// list
//
this.list.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawVariable;
this.list.IntegralHeight = false;
this.list.Location = new System.Drawing.Point(12, 12);
this.list.Name = "list";
this.list.Size = new System.Drawing.Size(604, 323);
this.list.TabIndex = 0;
this.list.DrawItem += new System.Windows.Forms.DrawItemEventHandler(this.list_DrawItem);
this.list.MeasureItem += new System.Windows.Forms.MeasureItemEventHandler(this.list_MeasureItem);
//
// Test
//
this.AutoScaleBaseSize = new System.Drawing.Size(6, 15);
this.ClientSize = new System.Drawing.Size(520, 358);
this.Controls.Add(this.list);
this.Name = "Test";
this.Text = "Display";
this.Load += new System.EventHandler(this.Test_Load);
this.Resize += new System.EventHandler(this.Display_Resize);
this.ResumeLayout(false);
}
#endregion
[STAThread]
static void Main()
{
Application.Run(new Test());
}
private void Test_Load(object sender, EventArgs e)
{
try {
// Loop all font families
FontFamily[] families = FontFamily.Families;
foreach (FontFamily family in families) {
try { list.Items.Add(new Font(family, 20, FontStyle.Regular, GraphicsUnit.Pixel)); continue; }
catch { }
}
Display_Resize(this, EventArgs.Empty);
}
catch {
}
}
private void Display_Resize(object sender, System.EventArgs e)
{
Rectangle r = this.ClientRectangle;
list.SetBounds(list.Left,
list.Top,
r.Width - (list.Left * 2),
r.Height - (list.Top + list.Left));
}
public string TextValue = "Example String";
public StringFormat Format
{
get
{
StringFormat format = StringFormat.GenericTypographic;
format.FormatFlags |= StringFormatFlags.NoWrap;
return format;
}
}
private void list_DrawItem(object sender, System.Windows.Forms.DrawItemEventArgs e)
{
Brush back = null;
Brush fore = null;
Brush htext = null;
Rectangle r;
try {
Font font = (Font)list.Items[e.Index];
// Loop
if ((e.State & DrawItemState.Selected) != 0) {
back = new SolidBrush(Color.DarkBlue);
fore = new SolidBrush(Color.White);
htext = new SolidBrush(Color.Orange);
}
else {
back = new SolidBrush(Color.White);
fore = new SolidBrush(Color.Black);
htext = new SolidBrush(Color.DarkRed);
}
// Fill the rect
e.Graphics.FillRectangle(back, e.Bounds);
// Get the size of the header
SizeF szHeader = e.Graphics.MeasureString(font.Name, head, int.MaxValue, Format);
SizeF szText = e.Graphics.MeasureString(TextValue, font, int.MaxValue, Format);
// Draw the string
r = e.Bounds;
r.Height = (int)szHeader.Height;
e.Graphics.DrawString(font.Name, head, htext, r, Format);
// Draw the string
r = e.Bounds;
r.Y = (int)(e.Bounds.Y + szHeader.Height);
r.Height = (int)szText.Height;
e.Graphics.DrawString(TextValue, font, fore, r, Format);
}
catch {
}
finally {
if (fore != null) fore.Dispose();
if (back != null) back.Dispose();
if (htext != null) htext.Dispose();
}
}
private void list_MeasureItem(object sender, System.Windows.Forms.MeasureItemEventArgs e)
{
try {
Font font = (Font)list.Items[e.Index];
SizeF szHeader = e.Graphics.MeasureString(font.Name, head, int.MaxValue, Format);
SizeF szText = e.Graphics.MeasureString(TextValue, font, int.MaxValue, Format);
// Return it
e.ItemHeight = (int)(szText.Height + szHeader.Height);
e.ItemWidth = (int)Math.Max(szText.Width, szHeader.Width);
}
catch {
}
}
}
}