Creating an image button in .NET Winforms application - c#

I'm trying to create a button in my .NET 4.0 Winforms app in Visual Studio 2010 that is ONLY an image. I have a window that is borderless and has a background image that makes up my custom skin for this application. For the close/minimize buttons in the top right of the window, I wanted to create 2 simple buttons that are images only that look like the typical Windows close/minimize buttons.
I may be going about this design wrong, so if I am please let me know. So far I've determined that I need to create a subclass for Button that only renders the image. The final implementation needs to render different images for each button state (normal, hover, clicked, etc). Here is what I have so far:
public class ImageButton : Button
{
Pen pen = new Pen( Color.Red, 1.0f );
public ImageButton()
{
SetClientSizeCore( BackgroundImage.Width, BackgroundImage.Height );
}
protected override void OnPaint( PaintEventArgs e )
{
e.Graphics.DrawImage( BackgroundImage, 0, 0 );
//e.Graphics.DrawRectangle( pen, ClientRectangle );
//Rectangle bounds = new Rectangle( 0, 0, Width, Height );
//ButtonRenderer.DrawButton( e.Graphics, bounds, PushButtonState.Normal );
//base.OnPaint(pevent);
}
protected override void OnPaintBackground( PaintEventArgs e )
{
// Do nothing
}
}
At this point, assuming this design is appropriate, I need to know how to call SetClientSizeCore() appropriately. Calling it in the constructor raises an exception. I assume this is because the control hasn't had a chance to initialize yet. I'm not sure what function to override that will allow me to change the size of my button to fit the image after it has been initialized by .NET. Any ideas on this?

In the constructor, BackgroundImage is null.
You need to set the size when BackgroundImage is changed by overriding the property.
You should also shadow the Size property and add [DesignerSerializationVisibilty(DesignerSerializationVisibility.Hidden)] to prevent the size from being saved by the designer.

Wait until the BackgroundImage property is assigned so you'll know what size you need. Override the property like this:
public override Image BackgroundImage {
get { return base.BackgroundImage; }
set {
base.BackgroundImage = value;
if (value != null) this.Size = value.Size;
}
}

If you want to use ImageButtons, I'd recommend using BunifuUI as it has ImageButtons.
If you don't want to use BunifuUI, you can use a PictureBox as it also has a click event. Example:
private void pictureBox1_Click(object Sender, EventArgs e) {
webBrowser1.Navigate("https://www.google.com");
}

Related

Fix background crush C# winforms

I work on MDIParent with background image after i move a child from in it the background image crush like this
how can i solve this issue in C# winforms??
The MDIClient Control is the object used, in Multiple-Document Interface, as the Container for all child Forms.
This Control has a BackGroundImage property. You can set an Image object using this property, instead of using the [MDIParent].BackGroundImage. This would actually solve the Image tearing problem.
But, you can't set a specific Layout property. See the Docs about the MDIClient BackgroundImageLayout:
This property is not relevant to this class.
You can set this property, but it's ignored: the default ImageLayout.Tile is used instead.
A different Layout can be set, assigning the Image object to the MDIParent BackGroundImage and specifying a BackGroundImageLayout. This will change the Layout, but it will also cause the tearing effect you are reporting.
A possible solution is to draw the Image object on the MDIClient surface, using it's Paint() event as usual.
This will solve the tearing effect. Not the flickering; this can be noticed when you resize the MDIParent Form (well, MDI applications are not resized that often, maybe maximized and normalized).
Some flickering can be seen when the background Image is not covered by a child Form.
A small adjustment to the Image specs is required: setting its DPI resolution = to the MDIParent reported device context DPI, otherwise the Image size will not match the default size (it's affected by the DPI resolution).
See this post for a description:
Image is not drawn at the correct spot.
An example:
(Here, I assume the background Image is loaded from the Project resources)
public partial class MDIParent : Form
{
private MdiClient mdiBackground = null;
private Bitmap BackGroundImage = null;
public MDIParent()
{
InitializeComponent();
//Specify an existing Resources' Image
BackGroundImage = new Bitmap(Properties.Resources.[Some Res Image] as Bitmap);
BackGroundImage.SetResolution(this.DeviceDpi, this.DeviceDpi);
}
private void Form1_Load(object sender, EventArgs e)
{
mdiBackground = this.Controls.OfType<MdiClient>().First();
mdiBackground.Paint += (s, evt) => {
evt.Graphics.DrawImage(BackGroundImage,
(mdiBackground.Width - BackGroundImage.Width) / 2.0F,
(mdiBackground.Height - BackGroundImage.Height) / 2.0F);
};
//Show some child Forms on start-up
}
private void Form1_Resize(object sender, EventArgs e)
{
if (this.mdiBackground != null) mdiBackground.Invalidate();
}
private void MDIParent_FormClosed(object sender, FormClosedEventArgs e)
{
if (BackGroundImage != null) BackGroundImage.Dispose();
}
}

Draw a Clickable Square?

I am new to C# but I have a lot of Java experience so I've been told that C# is fairly easy to comprehend based on that.
So far it is. At the moment though, I want to make a simple TicTacToe as part of an exercise. However what I want to do, is draw clickable squares that I can reference so I can check if the box is already clicked or not.
I am currently using Visual Studio Express 2012. I am making a Windows Form Application for Desktop usage.
I looked around for solutions but I can't seem to find something that does this.
How would I go about doing this?
internal sealed class Box : Control
{
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.DrawRectangle(Pens.Black, new Rectangle(Point.Empty, new Size(Width - 1, Height - 1)));
}
protected override void OnClick(EventArgs e)
{
MessageBox.Show("You Clicked The Box!");
}
}
Create a class that derives from Control. From here you can override a whole bunch of virtual members including the OnPaint method where you can perform all of your drawing logic. It's all very intuitive with the assistance of IntelliSense (DrawRectangle, Draw Line etc).
If you want you can override OnClick like I did here but otherwise you can subscribe to an the controls' Click event just as you would a standard control.
You can also derive from ContainerControl for your 'grid' which will behave similarly to a Panel or GroupBox control.
Here's a quick example I just put together to get you started. The border is a tiny bit bugged at some resolutions, I'll leave that down to my abysmal mathematics skills.
internal sealed class GameGrid : ContainerControl
{
protected override void OnCreateControl()
{
for (int y = 0; y < 3; y++)
{
for (int x = 0; x < 3; x++)
{
GameButton button = new GameButton
{
Width = Width/3,
Height = Height/3,
};
button.Location = new Point(x*button.Width++, y*button.Height++);
Controls.Add(button);
button.Click += button_Click;
}
}
}
static void button_Click(object sender, EventArgs e)
{
GameButton gameButton = (GameButton)sender;
gameButton.CircleCheck = true;
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.SmoothingMode = SmoothingMode.HighQuality;
e.Graphics.DrawRectangle(Pens.Black, new Rectangle(ClientRectangle.Location, new Size(Width - 1, Height - 1)));
}
}
internal sealed class GameButton : Control
{
private bool _cricleCheck;
public bool CircleCheck
{
get
{
return _cricleCheck;
}
set
{
_cricleCheck = value;
Invalidate();
}
}
private readonly Pen circlePen = new Pen(Brushes.Black, 2.0f);
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.SmoothingMode = SmoothingMode.HighQuality;
e.Graphics.DrawRectangle(Pens.Black, new Rectangle(ClientRectangle.Location, new Size(Width - 1, Height - 1)));
if (CircleCheck)
{
e.Graphics.DrawEllipse(circlePen, new Rectangle(ClientRectangle.Location.X + 10, ClientRectangle.Location.Y + 10, Width - 30, Height - 30));
}
}
}
The most common clickable square is a button.
If the button is clickable in the current gamestate and what icon should be presented there if it was clicked sounds like a job for your background logic.
Try this:
Create a new Form
Drop a PictureBox control inside the form in order to render the Tic Tac Toe board
Handle the Paint event to do the graphics rendering
Handle the MouseClick event to detect if the user has clicked inside the board and use the event arguments to determine on which of the 9 squares of the board the user has clicked
Hope this helps.
I did a similar exercise 2days ago,
Just check this tutorial, it will be enough I guess:
http://www.codeproject.com/Articles/2400/Tic-Tac-Toe-in-C
And for the Square you can either use buttons or you could use PictureBox like this tutorial (although they used vb.net not c# but it's easy to translate):
http://vbprogramming.8k.com/tutorials/tic-tac-toe.htm
I think the easiest solution is using simple buttons with background color or image.
Have a variable for currently playing user and change
button.Text
on click event.
Set all button texts for "" at the beginning- that way you can check if the button was clicked:
if (button.Text!="")
//it was already clicked

C# Draw Line OnPaint() vs CreateGraphics()

Question:
How do you properly draw on a winform from a method other than the OnPaint() method?
Additional Information:
The code I have now draws some background lines for a TicTacToe game in the OnPaint() method. Then I use the Mouse_Click event and am running this code which apparently is not proper:
private void TicTacToe_MouseClick(object sender, MouseEventArgs e)
Graphics g = this.CreateGraphics();
g.DrawEllipse(this.penRed, this.Rectangle);
For reasons I do not understand, it does draw the circle, but when minimizing or moving the form off screen it erases the circles but not the lines from the OnPaint() method.
You are doing a lot of "view" but no "model".
When you want to create a shape, when the mouse button goes down/up, create some DATA representing the shape.
Your data structures represent the persistent information (it is the data that allows you to save and load this information between sessions).
All your paint function needs to do is look at the DATA structures and paint it. This will therefore persist between sizing/hiding/showing.
The problem is that Windows windows (that includes WinForms) have no graphical memory of their own unless their creator provides such memory and it is only a matter of time before that particular window wilk get overwritten or hidden and eventually need to be repainted. You're painting to the screen ( you might say ) and others can do the same. The only convention you can rely on is that the OnPaint will get called when needed. Basically it's alright to use your philosophy and draw whenever you need to (not on some misterious and unpredictable schedule). For that check out my solution.
You should use a "backbuffer bitmap" like:
private Bitmap bb;
protected override void OnResize(EventArgs e) {
this.bb = new Bitmap(this.ClientSize.Width, this.ClientSize.Height);
this.InitBackBuffer();
}
private void InitBackBuffer() {
using (var g = Graphics.FromImage(this.bb)) {
// do any of the "non dissapearing line" drawing here
}
}
private void TicTacToe_MouseClick(object sender, MouseEventArgs e)
using (Graphics g = Graphics.FromImage(this.bb))
g.DrawEllipse(this.penRed, this.Rectangle);
this.Invalidate();
}
protected override void OnPaint(PaintEventArgs e) {
base.OnPaint(e);
e.Graphics.DrawImageUnscaled(this.bb);
}
Try that. That should do it :)
What you are doing is drawing on the form "asynchronously" (from the OnPaint method). You see, the OnPaint method is what Windows Forms relies on to draw your entire form. When something happens to your From, it is invalidated and OnPaint is called again. If something isn't drawn in that method, then it will not be there after that happens.
If you want a button to trigger something to appear permanently then what you need to do is Add that object to a collection somewhere, or set a variable related to it. Then call Refresh() which calls Invalidate() and Update() Then, during OnPaint, draw that object (ellipis).
If you want it to still be there after something happens to your form, such as minimize, you have to draw it during OnPaint.
Here's my suggestion:
public partial class Form1 : Form
{
Rectangle r = Rectangle.Empty;
Pen redPen = new Pen(Color.Red);
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
r = new Rectangle(50, 50, 100, 100);
Refresh();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
if (r != Rectangle.Empty)
{
e.Graphics.DrawRectangle(redPen, r);
}
}
}

Making a background image scale with button size

I'm trying to add some background images to a few buttons in my Win Forms application. The three images are different sizes (ie pixel dimensions don't match, one is 128x128 and another is 256x256). I need the buttons to be identical in size (otherwise the GUI is horribly asymmetrical). Without changing the actual image files, how can I get the images to scale with button size?
I've tried creating my own class, and adding an event handler for the button resize event, but that doesn't seem to work. My code:
class CustomButton : Button {
internal void CustomButton_Resize( object sender, EventArgs e ) {
if ( this.BackgroundImage == null ) {
return;
}
var pic = new Bitmap( this.BackgroundImage, this.Width, this.Height );
this.BackgroundImage = pic;
}
}
and in the form:
this.buttonOne.Resize += new System.EventHandler(this.buttonOne.CustomButton_Resize);
Forgot to mention, the above code does not resize the images at all. The buttons still need to have different sizes to display the images completely.
Easiest way to add a background image to a .NET Button object and scale it to fit
I used this method to avoid any additional coding of new classes and event handlers. This helped me also avoid converting all Button objects into Image objects.
Add image to your Resources.resx file.
Click on your chosen button.
Navigate to the BackgroundImage property and choose the image you imported into the project's resources.resx file.
Navigate to the BackgroundImageLayout property and choose Stretch.
Make sure you don't have anything entered for the Image and Text properties or else they will interfere with your new background image.
The easy programmatic way
Say I have a button btn1, Following code is working perfectly in visual-studio-2010.
private void btn1_Click(object sender, EventArgs e)
{
btn1.Width = 120;
btn1.Height = 100;
}
void btn1_Resize(object sender, EventArgs e)
{
if ( this.BackgroundImage == null )
return;
var bm = new Bitmap(btn1.BackgroundImage, new Size(btn1.Width, btn1.Height));
btn1.BackgroundImage = bm;
}
The better way
You can add eventHandler in the constructor of your custombutton (just to ensure that you are adding eventhandler correctly)
class CustomButton : Button
{
CustomButton()
{
this.Resize += new System.EventHandler(buttonOne.CustomButton_Resize);
}
void CustomButton_Resize( object sender, EventArgs e )
{
if ( this.BackgroundImage == null )
return;
var pic = new Bitmap( this.BackgroundImage, new Size(this.Width, this.Height) );
this.BackgroundImage = pic;
}
}
Now when you will resize the button anywhere your image will get fit(scaled) to its new size.
You could start with something like this...
public class ImageButton : Control
{
public Image backgroundImage;
public Image BackgroundImage
{
get
{
return backgroundImage;
}
set
{
backgroundImage = value;
Refresh();
}
}
public ImageButton()
{
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.Clear(BackColor);
if(BackgroundImage != null)
e.Graphics.DrawImage(BackgroundImage, 0, 0, Width, Height);
base.OnPaint(e);
}
protected override void OnPaintBackground(PaintEventArgs pevent)
{
//base.OnPaintBackground(pevent);
}
}
You can just handle paint and draw the image yourself. You may also try using a PictureBox or some other control which has more scaling options

Draw an image out of a control

I have a control. It's a text box. I want to draw an image at the left of the control. The image should be painted outside the control. I could paint it one but inside.
Here is the code:
private static Image requiredIcon = Resources.Icon_required;
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
if (base.Enabled && string.IsNullOrEmpty(base.Text))
{
e.Graphics.DrawImage(requiredIcon, 0, 0);
}
}
Instead of drawing the image, you could create an appropriate control, e.g. PictureBox, with it's Image property set to the appropriate image resource.
It may be easier to do this at design time rather than run time. If the image should not be shown intitially, set it's Visible property to false at design time and set it to true at run time when the image should be shown.
Create a user control composed of a TextBox and a PictureBox. Then in the OnPaint event of your user control you will be able to draw outside the textbox (in the PictureBox control).
Here's a picture of what the UserControl could look like. It contains a PictureBox and a TextBox.
If you just want to draw an icon you just have to assigned it to the PictureBox. So the OnPaint may not be necessary in your case.
Instead of overloading the TextBox.OnPaint method, you have to overload the Form's (or whatever is the parent component of the TextBox) OnPaint method, and paint the Image at the left hand side of the TextBox. You can't have the TextBox draw to an area outside its own bounds.
I can get a parent and use it for painting.
protected override void OnParentChanged(EventArgs e)
{
base.OnParentChanged(e);
base.Parent.Paint += new PaintEventHandler(Parent_Paint);
}
private void Parent_Paint(object sender, PaintEventArgs e)
{
if (base.Enabled && string.IsNullOrEmpty(base.Text))
{
e.Graphics.DrawImage(requiredIcon, 0, 0);
}
}

Categories

Resources