When in Foobar2000 I drag-and-drop files into playlist, the ListView shows the place of dropping tracks:
I'm looking for a way to implement this feature. Is it just drawn by Graphics library, or do I have some implemented method to show horizontal line on ListView?
At least with a ListView control, there is some limited built-in support provided by way of the InsertionMark property, but this only works in icon view, small icon view, and tile view. It doesn't work when the Items are sorted or when groups are turned on, and it requires comctrl32.dll version 6 (thus meaning Windows XP or later, with Visual Styles enabled).
If you want a more general solution, you can easily draw a line yourself in the DragOver event:
To avoid flicker we remember the last index:
int prevItem = -1;
The event uses a HitTest to determine the item under the cursor..:
private void listView1_DragOver(object sender, DragEventArgs e)
{
Point mLoc = listView1.PointToClient(Cursor.Position);
var hitt = listView1.HitTest(mLoc);
if (hitt.Item == null) return;
int idx = hitt.Item.Index;
if (idx == prevItem) return;
listView2.Refresh();
using (Graphics g = listView1.CreateGraphics())
{
Rectangle rect = listView1.GetItemRect(idx);
g.DrawLine(Pens.Red, rect.Left, rect.Top, rect.Right, rect.Top);
}
prevItem = idx;
}
If you want to use a ListBox, the code is pretty much the same:
private void listBox1_DragOver(object sender, DragEventArgs e)
{
Point mLoc = listBox1.PointToClient(Cursor.Position);
int idx = listBox1.IndexFromPoint(mLoc);
if (idx < 0) return;
if (idx == prevItem) return;
listBox1.Refresh();
using (Graphics g = listBox1.CreateGraphics())
{
Rectangle rect = listBox1.GetItemRectangle(idx);
g.DrawLine(Pens.Red, rect.Left, rect.Top, rect.Right, rect.Top);
}
prevItem = idx;
}
Also do a Refresh to clear the line in the DragLeave event and also in the DragDrop event!
Note the this is one of the rare situations when you want to use control.CreateGraphics because the drawing is meant to be non-persistent! Normally all graphics should be drawn with the e.Graphics object in the Paint event!
Related
I have a C# WinForms project where I have to paint some things on my panel. I load in a grid from a file that translates to squares that are painted on the panel. Then I load in a file with dots that are then painted on top of the squares.
I then have a function that moves the dots around. But the repaint function is called every tick which causes the whole grid to flicker continously because it is painted so quick after each other.
How do I make it so that only the dots are repainted?
The repaint and paint functions are as follows:
private void Repaint(object sender, EventArgs e)
{
GridPanel.Invalidate();
}
private void GridPanel_Paint(object sender, PaintEventArgs e)
{
if ((GridPanel.Width != grid.Width || GridPanel.Height != grid.Height))
{
grid.Height = GridPanel.Height;
grid.Width = GridPanel.Width;
grid.setDimensions();
}
Graphics g = e.Graphics;
g.FillRectangle(new SolidBrush(Color.White), new Rectangle(0, 0, grid.Width, grid.Height));
foreach(Square square in grid.Squares)
{
if (square.Letter != '_')
{
SolidBrush color = new SolidBrush(square.Color);
g.FillRectangle(color, new Rectangle(square.X * grid.squareWidth, square.Y * grid.squareHeight, grid.squareWidth, grid.squareHeight));
}
}
foreach(Artist artist in grid.Artists)
{
SolidBrush color = new SolidBrush(artist.Color);
g.FillRectangle(color, new Rectangle(Convert.ToInt32(artist.X * grid.squareWidth), Convert.ToInt32(artist.Y * grid.squareHeight), grid.artistWidth, grid.artistHeight));
}
}
I also tried to use a second panel for the dots so I only have to repaint that one. But I cant get a transparent background working on the second panel, so the first panel is not visible this way.
Somebody knows a good solution for this problem?
I am using Microsoft Visual Studio Community 2017 version 15.7.2, and .NET Framework version 4.7.03056.
I am using the Winforms TreeView and am modifying its default behavior to make it a little bit more like the Windows Explorer tree view. I set the following properties:
LineHeight` 22
DrawMode OwnerDrawAll
I am using the following for the DrawNode event. This code uses right and down bracket bitmaps (which are 16x16) to show expanded or unexpanded nodes, and uses custom colors for select/focus highlighting. Nothing exotic.
private void treeDir_DrawNode(object sender, DrawTreeNodeEventArgs e)
{
const int indent = 12;
const int markerSpacing = 20;
int leftPos = e.Bounds.Left + e.Node.Level * indent;
Brush selectBrush;
Pen pen;
Graphics g = e.Graphics;
e.DrawDefault = false;
if (e.Node.IsSelected)
{
if (e.Node.TreeView.Focused)
{
selectBrush = new SolidBrush(FocusedBackgroundColor);
pen = new Pen(new SolidBrush(FocusedPenColor));
}
else
{
selectBrush = new SolidBrush(UnfocusedBackgroundColor);
pen = new Pen(new SolidBrush(UnfocusedPenColor));
}
g.FillRectangle(selectBrush, e.Bounds);
g.DrawRectangle(pen, e.Bounds);
}
if (e.Node.Nodes.Count > 0)
{
if (e.Node.IsExpanded)
{
g.DrawImage(Properties.Resources.Expanded, leftPos+2, e.Bounds.Top+2);
}
else
{
g.DrawImage(Properties.Resources.Unexpanded, leftPos+2, e.Bounds.Top+2);
}
}
g.DrawString(
e.Node.Text, CommonFont, new SolidBrush(Color.Black), leftPos + markerSpacing, e.Bounds.Top+2);
}
What's happening is that when the form is first shown, if I expand a node that is not the first node, it also overwrites (transparently overlays) the first node text. Here's the sequence.
On start up of the form:
Then I double click Node 4:
If I double click the first node, the problem clears up:
From this point forward, if I double click Node 4, the problem no longer occurs. Double clicking the first node clears up the problem and avoids it for the life of the form after that point for Node 4. However, if I double click another expandable node further down, it happens again.
Is this a bug in TreeView or am I doing something incorrect in my owner draw?
The DrawNode event is called rather too often when doubleclicking and one set of calls has a bounds rectangle that is Empty.
(Maybe the reasoning was: If all drawing is happening only in the empty rectangle nothing will show. Hm..)
So as a workaround you can shortcut the DrawNode event for all the wrong calls at the beginning of the event:
if (e.Bounds.Height < 1 || e.Bounds.Width < 1) return;
I also recommend text rendering like this..:
TextRenderer.DrawText(g, e.Node.Text, CommonFont,
new Point( leftPos + markerSpacing, e.Bounds.Top+2), Color.Black);
TextRenderer is always recommended over Graphics.DrawString for forms as it improves on several shortcomings.
My problem is that within my Windows Form Application, I want to draw an Ellipse everytime the mouse is clicked within a specific picture box, and I want the previously drawn ellipses to remain present in the picture box.
In its current state, once the mouse is clicked, the previously drawn ellipse will be replaced with the new one drawn at the cursor's new location.
Ball.Paint draws an ellipse.
Here is the relevant code to the problem:
private Ball b;
private void pbField_Paint(object sender, PaintEventArgs e)
{
if (b != null)
b.Paint(e.Graphics);
}
private void pbField_MouseClick(object sender, MouseEventArgs e)
{
int width = 10;
b = new Ball(new Point(e.X - width / 2, e.Y - width / 2), width);
Refresh();
}
If there is any more needed code or information I am able to provide it.
You need some sort of data structure to store prior ellipses. One possible solution is below:
private List<Ball> balls = new List<Ball>(); // Style Note: Don't do this, initialize in the constructor. I know it's legal, but it can cause issues with some code analysis tools.
private void pbField_Paint(object sender, PaintEventArgs e)
{
if (b != null)
{
foreach(Ball b in balls)
{
b.Paint(e.Graphics);
}
}
}
private void pbField_MouseClick(object sender, MouseEventArgs e)
{
int width = 10;
b = new Ball(new Point(e.X - width / 2, e.Y - width / 2), width);
balls.Add(b);
Refresh();
}
If you want more than one ball to be painted, you need to keep track of a list of balls, rather than just b. Every time the control is refreshed, it is expected to redraw all its contents. That means that in pbField_Paint, you need to be ready to draw as many balls as have been added to the scene.
I have a userControl which has some programmatically drawn rectangles. I need few instances of that userControl on my form (see the image). The problem is that only the last instance will show the drawn shapes!
I guess it has something to do with drawing surface or the Paint event handler
In case it might help, here's some of the code I use in my control:
private void MyUserControl_Paint(object sender, PaintEventArgs e)
{
showHoraireMaitresse();
Rectangle rec = showDisponibilités();
var b = new SolidBrush(Color.FromArgb(150, Color.Blue));
e.Graphics.FillRectangle (b, rec);
showOccupation();
}
private void showHoraireMaitresse()
{
heureDebut = 8;
for (int i = 0; i < 14; i++)
{
//Label d'heure -> This shows just fine
addLabel(i, heureDebut);
//Rectangles d'heure -> This shows only in last instance
var rectangle = new Rectangle(180 + i * largeurDUneHeure, 14, largeurDUneHeure, 30);
surface.DrawRectangle(defaultPen, rectangle);
}
addLabel(14, heureDebut);
}
Thank you!
Without further information, I'm going to guess that 'surface' is static.
Trace through OnPaint and check which control is painting, and what the bounds are for 'surface'. Perhaps all the controls are painting the same exact rectangle.
On a blank winform code can be added to show lines that intersect (crosshairs) at the mouse pointer. The problem is that the lines don't show (or are partially hidden) by controls on the form (ie listview, splitcontainer, buttons).
How would I modify the code below to show on-top (bring to front...) of all the controls present on the form?
int lastX = 0;
int lastY = 0;
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
Region r = new Region();
r.Union(new Rectangle(0, lastY, this.Width, 1));
r.Union(new Rectangle(lastX, 0, 1, this.Height));
this.Invalidate(r);
this.Update();
Graphics g = Graphics.FromHwnd(this.Handle);
g.DrawLine(Pens.Chocolate, 0, e.Y, this.Width, e.Y);
g.DrawLine(Pens.Chocolate, e.X, 0, e.X, this.Height);
lastX = e.X;
lastY = e.Y;
}
private void Form1_MouseLeave(object sender, EventArgs e)
{
this.Invalidate();
}
You need a transparent window that's on top of all the other controls. The only way to get one is by overlapping the form with another form, made transparent with its TranparencyKey property. You'll find sample code for this in my answer in this thread.
Please just try first sending to back(Control.SendToBack()) the controls on the form (ie listview, splitcontainer, buttons). Put this at the FormLoad event. I have experimented the same nightmare with a Windows MDI application.
Hope that helps,
Enumerate through the desired controls and call the .BringToFront(); function on them.
listBox1.BringToFront();
According to the documentation, the region object should be in world co-ordinates, you're passing in client co-ordinates. Use Control.PointToScreen to map the rectangles' top left coordinate to world space.
I'd also be tempted to defer the drawing to the OnPaint method.