I'm building custom user control that will be used to display tiles map, as base class I've chosen ScrollableControl, because I want to have scrollbars in my control.
I've successfully created paint logic that is responsible for painting only needed elements.
Now I'm trying to add static text that will be always visible in same place (in my case white box with red text in top left corner):
This isn't clearly visible on above gif, but that white box blinks and jumps a bit when I scroll using mouse or scrollbars.
My question is how should I change my code to have scrollable content and fixed position content on top of that scrollable content?
Is ScrollableControl good choice as base class?
Below is my code:
class TestControl : ScrollableControl
{
private int _tileWidth = 40;
private int _tileHeight = 40;
private int _tilesX = 20;
private int _tilesY = 20;
public TestControl()
{
SetStyle(ControlStyles.ResizeRedraw, true);
SetStyle(ControlStyles.UserPaint, true);
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
SetStyle(ControlStyles.Opaque, true);
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
UpdateStyles();
ResizeRedraw = true;
AutoScrollMinSize = new Size(_tilesX * _tileWidth, _tilesY * _tileHeight);
Scroll += (sender, args) => { Invalidate(); };
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
e.Graphics.FillRectangle(new SolidBrush(BackColor), ClientRectangle);
e.Graphics.TranslateTransform(AutoScrollPosition.X, AutoScrollPosition.Y);
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
var offsetX = (AutoScrollPosition.X * -1) / _tileWidth;
var offsetY = (AutoScrollPosition.Y * -1) / _tileHeight;
var visibleX = Width / _tileWidth + 2;
var visibleY = Height / _tileHeight + 2;
var x = Math.Min(visibleX + offsetX, _tilesX);
var y = Math.Min(visibleY + offsetY, _tilesY);
for (var i = offsetX; i < x; i++)
{
for (var j = offsetY; j < y; j++)
{
e.Graphics.FillRectangle(Brushes.Beige, new Rectangle(i*_tileWidth, j*_tileHeight, _tileWidth, _tileHeight));
e.Graphics.DrawString(string.Format("{0}:{1}", i, j), Font, Brushes.Black, new Rectangle(i * _tileWidth, j * _tileHeight, _tileWidth, _tileHeight));
}
}
using (var p = new Pen(Color.Black))
{
for (var i = offsetX + 1; i < x; i++)
{
e.Graphics.DrawLine(p, i*_tileWidth, 0, i*_tileWidth, y*_tileHeight);
}
for (var i = offsetY + 1; i < y; i++)
{
e.Graphics.DrawLine(p, 0, i*_tileHeight, x*_tileWidth, i*_tileHeight);
}
}
e.Graphics.FillRectangle(Brushes.White, AutoScrollPosition.X * -1, AutoScrollPosition.Y * -1, 35, 14);
e.Graphics.DrawString("TEST", DefaultFont, new SolidBrush(Color.Red), AutoScrollPosition.X * -1, AutoScrollPosition.Y * -1);
}
}
EDIT:
I've searched a bit and found UserControl that has similar functionality - https://www.codeproject.com/Articles/16009/A-Much-Easier-to-Use-ListView and after reading a bit more on control's author blog http://objectlistview.sourceforge.net/cs/blog1.html#blog-overlays I found out that he is using Transparent Form that is positioned on top of control.
I really would like to avoid that, but still have overlay on top of my control.
You are doing battle with a Windows system option named "Show window content while dragging". Always turned on by default, this web page shows how to turn it off.
Solves the problem, but it is not something you can rely on since it affects all scrollable window in all apps. Demanding that the user turns it off for you is unrealistic, users like this option so they'll just ignore you. That they did not provide an option to turn it off for a specific window was a pretty major oversight. It is an okay solution in a kiosk app.
Briefly, the way the option works is that Windows itself scrolls the window content with the ScrollWindowEx() winapi function. Using a bitblt of the window content to move pixels and only generating a paint request for the part of the window that was revealed by the scroll. Usually only a few lines of pixels, so completes very fast. Problem is, that bitblt moves your fixed pixels as well. The repaint moves them back. Pretty noticeable, the human eye is very sensitive to motion like that, helped avoid being lion lunch for the past million years.
You'll have to take the sting out of ScrollWindowsEx(), preventing it from moving pixels even though you can't stop it from being called. That takes a heavy sledgehammer, LockWindowUpdate(). You'll find code in this post.
using System.Runtime.InteropServices;
...
protected override void OnScroll(ScrollEventArgs e) {
if (e.Type == ScrollEventType.First) {
LockWindowUpdate(this.Handle);
}
else {
LockWindowUpdate(IntPtr.Zero);
this.Update();
if (e.Type != ScrollEventType.Last) LockWindowUpdate(this.Handle);
}
}
[DllImport("user32.dll", SetLastError = true)]
private static extern bool LockWindowUpdate(IntPtr hWnd);
Not that pretty, using a separate Label control ought to start sounding attractive.
can you just add a label to that control(on top), in other words - cant you use it as panel?
Related
I am working on a program that displays the liveView image from a Nikon camera in a pictureBox. I want to be able to hover with the cursor over the image and display a zoomed in area around the cursor in another picturebox. I would also like to add a crosshair instead of mouse pointer. The only solution I have found so far is the following:
zoom an image in a second picturebox following cursor
It does exactly what I want, however I can not get it to work. More specifically, nothing is showing up in picZoom. In the example, images are loaded while in my case, a video stream is shown. That might be the reason why I am not getting it to work. I am relatively new to c#, and did not fully understand the example.
Lets say I have picBox where I receive the video stream. How do I show a portion of picBox around the cursor (let's say a rectangle around the cursor of dimensions x,y) in picZoom with a crosshair as in the example. I do not need to worry about differing dimensions of picBox and picZoom since they will not vary. I also want to be able to vary the degree of zoom by a factor zoomFactor.
If anyone could give me pointers or a solution, it would be greatly appreciated. Also, sorry if my question is poorly formatted, I am new to the forum.
Thank you!
Alexander
I would approach it like this
using System;
using System.Drawing;
using System.Windows.Forms;
namespace MagnifierExample
{
class Magnifier : PictureBox
{
public Magnifier()
{
Visible = false;
}
PictureBox source;
private Point sourcePoint;
Cursor oldCursor;
public PictureBox Source
{
get
{
return source;
}
set
{
if (source != null)
{
source.MouseEnter -= Source_MouseEnter;
source.MouseLeave -= Source_MouseLeave;
source.MouseMove -= Source_MouseMove;
source.Cursor = oldCursor;
}
source = value;
if (source != null)
{
source.MouseEnter += Source_MouseEnter;
source.MouseLeave += Source_MouseLeave;
source.MouseMove += Source_MouseMove;
oldCursor = source.Cursor;
source.Cursor = Cursors.Cross;
}
}
}
private void Source_MouseEnter(object sender, EventArgs e)
{
Visible = true;
BringToFront();
}
private void Source_MouseLeave(object sender, EventArgs e)
{
Visible = false;
}
private void Source_MouseMove(object sender, MouseEventArgs e)
{
sourcePoint = e.Location;
Location = new Point(source.Location.X + e.X - Width / 2, source.Location.Y + e.Y - Height / 2);
Invalidate();
}
protected override void WndProc(ref Message m)
{
const int WM_NCHITTEST = 0x0084;
const int HTTRANSPARENT = (-1);
if (!DesignMode && m.Msg == WM_NCHITTEST)
{
m.Result = (IntPtr)HTTRANSPARENT;
}
else
{
base.WndProc(ref m);
}
}
protected override void OnPaint(PaintEventArgs pe)
{
if (!DesignMode && Source != null && Source.Image != null)
{
Rectangle destRect = new Rectangle(0, 0, Width, Height);
// IMPORTANT: This calculation will depend on the SizeMode of the source and the amount you want to zoom.
// This does 2x zoom and works with PictureBoxSizeMode.Normal:
Rectangle srcRect = new Rectangle(sourcePoint.X - Width / 4, sourcePoint.Y - Height / 4, Width / 2, Height / 2);
pe.Graphics.DrawImage(Source.Image, destRect, srcRect, GraphicsUnit.Pixel);
}
else
{
base.OnPaint(pe);
}
}
}
}
To hook it up all you have to do is add a Magnifier to your form, give it a size, and set its Source to the PictureBox you want it to magnify.
this.magnifier1.Source = this.pictureBox1;
I used the approach in this answer to ignore messages from the magnifier window.
Importantly, I only coded up the zoom math for the PictureBoxSizeMode.Normal SizeMode of the source PictureBox. But I think this is a pretty good start.
Not 100% sure what you wanted for crosshairs, but this uses the built-in crosshair cursor.
I am trying to make a rss news ticker which will display the text,the text need move from left to right
I made the the code and text is moving from left to right but after a specific time its not showing the full text,i will be adding more news from the admin pannel,each time i add the news the text is not showing after the first scroll
the below screenshot is after a specific amount of time,only a part of news is displaying
Code used
int x = -800,y=1;
this.timer1.Tick += new System.EventHandler(this.timer1_Tick);
private void timer1_Tick(object sender, System.EventArgs e)
{
label1.SetBounds(x, y, 1, 1);
x++;
if(x>=800)
{
x = 4;
}
}
Code for reading xml
private void StartRssThread()
{
List<RssChannel> channels = new List<RssChannel>();
StringBuilder mergedFeed = new StringBuilder();
int mh = 0;
int ms = 0;
if (mh < 7)
{
RssFeed DaFeed = RssFeed.Read("http://shjc.ae/rss/fileName.xml");
RssChannel DaChannel = (RssChannel)DaFeed.Channels[0];
channels.Add(DaChannel);
mergedFeed.AppendFormat(" {0}: ", DaChannel.Title);
foreach (RssItem sTrm in DaChannel.Items)
{
if (ms < 10)
{
mergedFeed.AppendFormat(" {0} |", sTrm.Title);
ms++;
mh++;
}
}
}
string dafeed = mergedFeed.ToString();
mergedFeed = null;
textBox1.Invoke(new UpdateUiCallback(this.UpdateUi), new string[] { dafeed });
}
Windows Forms Marquee Label - Horizontal
I've already posted an example of how to create a Marquee Label to animate text from top to bottom, by using a Timer and overriding OnPaint method of a custom draw control in this post: Windows Forms Top to Bottom Marquee Label.
In the following code, I've changed that example to animates the text horizontally from right to left or left to right. It's enough to set RightToLeft property of the control to change the direction. Also don't forget to set AutoSize to false.
Example - Right to Left and Left to Right Marquee Label in Windows Forms
using System;
using System.Drawing;
using System.Windows.Forms;
public class MarqueeLabel : Label
{
Timer timer;
public MarqueeLabel()
{
DoubleBuffered = true;
timer = new Timer() { Interval = 100 };
timer.Enabled = true;
timer.Tick += Timer_Tick;
}
int? left;
int textWidth = 0;
private void Timer_Tick(object sender, EventArgs e)
{
if (RightToLeft == RightToLeft.Yes)
{
left += 3;
if (left > Width)
left = -textWidth;
}
else
{
left -= 3;
if (left < -textWidth)
left = Width;
}
Invalidate();
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.Clear(BackColor);
var s = TextRenderer.MeasureText(Text, Font, new Size(0, 0),
TextFormatFlags.TextBoxControl | TextFormatFlags.SingleLine);
textWidth = s.Width;
if (!left.HasValue) left = Width;
var format = TextFormatFlags.TextBoxControl | TextFormatFlags.SingleLine |
TextFormatFlags.VerticalCenter;
if (RightToLeft == RightToLeft.Yes)
{
format |= TextFormatFlags.RightToLeft;
if (!left.HasValue) left = -textWidth;
}
TextRenderer.DrawText(e.Graphics, Text, Font,
new Rectangle(left.Value, 0, textWidth, Height),
ForeColor, BackColor, format);
}
protected override void Dispose(bool disposing)
{
if (disposing)
timer.Dispose();
base.Dispose(disposing);
}
}
You are using hard coded values for the start and max value of x. From your question I think the text in the label has a dynamic length (correct?). If the text is of dynamic length the value of x should also be dynamic.
Also, x starts at -800. Then slowly grows to 800 and then it gets set to 4.
This seems strange to me, if the first run started at -800, the second run may also need to start at -800.
Hope this somewhat helped you. If not, please provide more details (like why you chose -800, 800 and 4).
I need to graph a polygonal on a panel (size 400, 400)
I try this, but it doesn't work.
PointF[] points = new PointF[totalpaso + 1];
for (int d = 0; d <= totalpaso; d++)
{
s = (float)(hola1[d] + 200);
w = (float)(hola2[d] + 200);
j = new PointF(s, w);
points[d] = j;
}
grafico.DrawPolygon(lapiz, points);
I think you should take a look at this post:
https://msdn.microsoft.com/en-us/library/07e699tw(v=vs.110).aspx
But with only that method you won't get far. It'll be drawn in a OnPaint Method:
C# Forms - Using Paint methods?
You will probably want to make your own control based on Panel.
A really basic example is this:
public sealed class MyPanel: Panel
{
public MyPanel()
{
this.ResizeRedraw = true;
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
using (var brush = new SolidBrush(this.ForeColor))
{
e.Graphics.FillEllipse(brush, 0, 0, this.Width, this.Height);
}
}
}
To test it, add the class to a Windows Forms project, compile, and then drop it onto a form and set it to "Dock" in the designer.
Then set the BackColor to, say, red and the ForeColor to blue, then run the program.
You can then add your required drawing code to the OnPaint() override.
I draw a rectangle panel with every button click. After that add a line on the edge of the rectangle and text on the center. But when I drag a panel move over other panel. The panel string will change. Please advice. I can't upload an image. How can I upload an image like that can show my problem more clearly.
This link http://i3.photobucket.com/albums/y53/ctkhai/1-4.png show the gui of my software. The upper left and lower left are picturebox. User add a "box" when click on button once and the "box" will show on upper left. User can drag the box to lower right and arrange all the box.
now the problem is when user drag the new added "box" and move over some other "box", the text i draw on previous box will change. like this http://i3.photobucket.com/albums/y53/ctkhai/2-4.png.
Update: I try to create a class for the tag. but it won't work, the number change again. It is the way I create class for the tag and read is wrong? code like below
Product _box = new Product();
List<Panel>product = new List<Panel>();
public class Product
{
public float X { set; get; } //box coordinate
public float Y { set; get; } //box coordinate
public int rotate { set; get; }
public int entryP { set; get; }
public int boxName { set; get; }
}
private void button_RecAdd_Click(object sender, EventArgs e)
{
locX = pictureBox_conveyor.Left + (pictureBox_conveyor.Width / 2 - box_y / 2);
locY = pictureBox_conveyor.Top + (pictureBox_conveyor.Height / 2 - box_x / 2);
_box.boxName = panelBoxNo;
_box.entryP = 1;
_box.rotate = 0;
_box.X = locX;
_box.Y = locY;
Panel box = new Panel();
box.Location = new Point(locX, locY);
box.Name = "box" + panelBoxNo;
box.Tag = _box;
box.Size = new Size(box_y, box_x);
box.BackColor = boxColor;
pbW = box.Width;
pbH = box.Height;
box.MouseDown += panelBox_MouseDown;
box.MouseMove += panelBox_MouseMove;
box.Paint += new PaintEventHandler((s, m) =>
{
Graphics g = m.Graphics;
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;]
Product b = box.Tag as Product;
string text = b.boxName.ToString();
SizeF textSize = m.Graphics.MeasureString(text, Font);
PointF locationToDraw = new PointF();
locationToDraw.X = (pbW / 2) - (textSize.Width / 2);
locationToDraw.Y = (pbH / 2) - (textSize.Height / 2);
g.DrawString(text, Font, Brushes.Black, locationToDraw);
g.DrawRectangle(new Pen(Color.Black), 0, 0, pbW - 1, pbH - 1);
g.DrawLine(drawLine, 0, 0, 0, pbH);
});
product.Add(box);
panel_pelletLayout.Controls.Add(box);
box.BringToFront();
label_boxNo.Text = panelBoxNo.ToString();
panelBoxNo++;
}
private void panelBox_MouseDown(object sender, MouseEventArgs e)
{
Panel p = sender as Panel;
if (e.Button == MouseButtons.Left)
{
xPos = e.X;
yPos = e.Y;
if (p != null)
{
activePnlBox = p.Name;
textBox_selectedName.Text = p.Name;
textBox_selectedX.Text = p.Left.ToString();
textBox_selectedY.Text = p.Top.ToString();
}
}
}
private void panelBox_MouseMove(object sender, MouseEventArgs e)
{
Panel p = sender as Panel;
if (p != null)
{
if (e.Button == MouseButtons.Left)
{
p.Left = ((e.X + p.Left - (p.Width / 2)) / gripGap) * gripGap;
p.Top = ((e.Y + p.Top - (p.Height / 2)) / gripGap) * gripGap;
textBox_selectedX.Text = p.Left.ToString();
textBox_selectedY.Text = p.Top.ToString();
}
}
}
Your Paint event handler has to be responsible for drawing everything each time. Your code looks like it only draws one object.
When you drag something over your box, the box becomes invalid and needs to be painted from scratch which means it erases everything and calls your Paint handler. Your Paint event handler then just draws one object.
I suspect that what you want to do is keep a data structure of each item you draw and then have a loop in your Paint event handler that will draw all the objects you add.
Don't use variables that you defined outside a loop, in the paint event. That might be your problem.
Try to paint ((Panel)s).Name. Does this work properly?
Your title has got all of us confused.. You don't draw Rectangles, you create new Panels on each ButtonClick, right?
The code for the Paint event is not quite right, though. Just like in any Paint event you should use the built-in Graphics object. And as Hans has noted, you should not destroy/dispose things you didn't create.
The main problem you describe seems to be that your boxes have to paint themselves without referring to their real numbers. You should store their numbers e.g. in their Tags..
(Or you could extract it from their Names, like you do it in the MouseDown!)
box[panelBoxNo].Name = "box" + panelBoxNo;
box[panelBoxNo].Tag = panelBoxNo; // < === !!
//..
box[panelBoxNo].Paint += new PaintEventHandler((s, m) =>
{
Graphics g = m.Graphics; // < === !!
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
string text = box[panelBoxNo].Tag.ToString(); // < ===
SizeF textSize = g.MeasureString(text, Font);
PointF locationToDraw = new PointF();
locationToDraw.X = (pbW / 2) - (textSize.Width / 2);
locationToDraw.Y = (pbH / 2) - (textSize.Height / 2);
g.DrawString(text, Font, Brushes.Black, locationToDraw);
g.DrawRectangle(new Pen(Color.Black), 0, 0, pbW - 1, pbH - 1);
g.DrawLine(drawLine, 0, 0, 0, pbH);
// g.Dispose(); // < === !!
// m.Graphics.DrawImageUnscaled(drawBox, new Point(0, 0)); // < === !!
// m.Dispose(); // < === !!
});
And, as I have noted you should only use arrays if you (or the code) really knows the number of elements. In your case a List<Panel> will be flexible to hold any number of elements without changing any other part of the code, except the adding. You can access the List just like an Array. (Which it is behind the scenes..)
Update: The way I see it now, your problems all are about scope in one way or another.
Scope in its most direct meaning is the part of your code where a variable is known and accessible. In a slightly broader meaning it is also about the time when it has the value your need.
Your original problem was of the latter kind: You accessed the partNo in thr Paint event of the Panels, when it had long changed to a new, probably higher value.
Your current problem is to understand the scope of the variables in the ButtonClick event.
Usually this is no problem; looking at the pairs of braces, the scope is obvious. But: Here we have a dynamically created Lambda event and this is completely out of the scope of the Click event!! Behind the scenes this Paint event is removed from the Click code, placed in a new event and replaced by a line that simply adds a delegate to the Paint handler just like any regular event.
So: nothing you declare in the ButtonClick is known in the Paint code!
To access these data you must place them in the Panel properties, in our case in the Tag and access them via casting from the Sender parameter s!
So you need to change this line in the Paint event
Product b = box.Tag as Product;
to something like this:
Product b = ( (Panel) s ).Tag as Product;
I have custom tab control where OnPaint method is override.
Then strange growth of tabs occurs. Tabs getting bigger (padding getting bigger) and they width depends on length of the text.
When I use default Tab Control - padding is OK. How to avoid this situation when I use UserPaint?
partial class Tab : TabControl
{
public Tab()
{
InitializeComponent();
Init();
}
private void Init()
{
this.SetStyle(ControlStyles.UserPaint, true);
this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
this.SetStyle(ControlStyles.DoubleBuffer, true);
this.SetStyle(ControlStyles.ResizeRedraw, true);
}
protected override void OnPaint(PaintEventArgs e)
{
DrawTabPane(e.Graphics);
}
private void DrawTabPane(Graphics g)
{
if (!Visible)
return;
// here we draw our tabs
for (int i = 0; i < this.TabCount; i++)
DrawTab(g, this.TabPages[i], i);
}
internal void DrawTab(Graphics g, TabPage tabPage, int nIndex)
{
Rectangle recBounds = this.GetTabRect(nIndex);
RectangleF tabTextArea = recBounds;
Point[] pt = new Point[4];
pt[0] = new Point(recBounds.Left + 1, recBounds.Bottom);
pt[1] = new Point(recBounds.Left + 1, recBounds.Top + 1);
pt[2] = new Point(recBounds.Right - 1, recBounds.Top + 1);
pt[3] = new Point(recBounds.Right - 1, recBounds.Bottom);
Brush br = new SolidBrush(clr_tab_norm);
g.FillPolygon(br, pt);
br.Dispose();
StringFormat stringFormat = new StringFormat();
stringFormat.Alignment = StringAlignment.Center;
stringFormat.LineAlignment = StringAlignment.Center;
br = new SolidBrush(clr_txt);
g.DrawString(tabPage.Text, Font, br, tabTextArea, stringFormat);
}
}
Turning on ControlStyles.UserPaint for controls that are built into Windows, like TabControl, is not the proper thing to do. I assume the bug is in GetTabRect(), it isn't visible in the snippet.
Instead, you should use the TabControl.DrawMode property and implement the DrawItem event. There's a good example in the MSDN Library.
It would appear from the image that your code is setting the size of the tabs to be wider than they need to be. The extra padding is present in all your tabs but it is just more visible in the tabs with longer text.
I can't be sure why this is but I'd guess that the code to calculate the size of the tabs (based on font metrics) is using a different font from that used to draw the tabs.