How to create a toggle button inside a column in C#? - c#

I want to create this view. But I don't know how to make this kind of button, is it feasible with C#? If so, Please help me.

For a ListView you can get something that looks like your image by
splitting the data into the Text and the Tags of each Item/Subitem
owner-drawing the ListView
storing the two images (including headroom) in two Bitmaps.
Coding the MouseDown event to bring the 'buttons' to live
Here is the code for owner-drawing.
private void listView3_DrawItem(object sender, DrawListViewItemEventArgs e)
{
Rectangle textBounds = e.Bounds; textBounds.Height /= 2;
e.Graphics.DrawImage(e.Item.Text == "True" ? bmpOn : bmpOff, e.Bounds.Location);
TextRenderer.DrawText(e.Graphics, e.Item.Tag.ToString(),
Font, textBounds, Color.Black, TFFcenter);
}
private void listView3_DrawSubItem(object sender, DrawListViewSubItemEventArgs e)
{
Rectangle textBounds = e.Bounds; textBounds.Height /= 2;
e.Graphics.DrawImage(e.SubItem.Text == "True" ? bmpOn : bmpOff, e.Bounds.Location);
TextRenderer.DrawText(e.Graphics, e.SubItem.Tag.ToString(),
Font, textBounds, Color.Black, TFFcenter);
}
private void listView3_DrawColumnHeader(object sender, DrawListViewColumnHeaderEventArgs e)
{
e.DrawDefault = true;
}
The code to process clicking on a ListView is simple but not obvious:
private void listView3_MouseDown(object sender, MouseEventArgs e)
{
ListViewHitTestInfo HI = listView3.HitTest(e.Location);
if (HI.SubItem != null) HI.SubItem.Text = HI.SubItem.Text == "True" ? "False" : "True";
else if (HI.Item != null) HI.Item.Text = HI.Item.Text == "True" ? "False" : "True";
}
The DrawText call uses TextFormatFlags to center the text in the upper half:
TextFormatFlags TFFcenter =
TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter;
The trick is in preparing the whole ListView.
For my test I used this routine:
ImageList il = new ImageList();
il.ImageSize = new Size(1, bmpOff.Height);
listView3.SmallImageList = il;
for (int c = 0; c < listView3.Columns.Count; c++)
listView3.Columns[c].Width = bmpOff.Width;
for (int i = 0; i < listView3.Columns.Count; i++)
{
ListViewItem lvi = new ListViewItem( (i % 2 == 0).ToString() );
for (int c = 0; c < 5; c++)
{
lvi.SubItems.Add( ( (c+i) % 2 == 0).ToString());
lvi.SubItems[c].Tag = "3/7";
}
lvi.Tag = "3/7";
listView3.Items.Add(lvi);
}
listView3.Width = listView3.Columns.Count * bmpOff.Width + 4;
Note how I use a dummy ImageList to enforce the Item heights..but while you're at it, it is probably a better idea to add the ImageList in the Designer and store the Bitmaps in it..
When you want to access/change the text displayed above the buttons you need to use the Tag of each Item/Subitem..!
It is also possible to use a DataGridView and get the same look by cell painting it.
Note that none of the solutions lends itself well to databinding as you have too many data to bind per item/cell!

In Windows Forms. (That it looks like what you are using.) You can drop a DataGridView on a form and change the ColumnType in the GridView designer to DataGridViewCheckBoxColumn. This will give you the Functionality that you are looking for.
In other words, if you dont mind the look, (But need the same functionality) you could use a Checkbox in your grid.
When you have the correct functionality : You can use the above answer from Gnqz, to get the Look of the button.

Related

ListView Columns/GridlinesOffset/Alignment error [duplicate]

I am trying to write a Windows Forms MusicPlayer application in C#.
The application should show a list and have some play / stop buttons.
I just started half an hour ago, but my design is almost finished. Now I got 3 things to fix. A bug and 2 good looking things:
on the picture you can see the bug I've found. You might say that's nothing, but its a eye catcher. How can I fix this?
how can I align center the headline of a column, without centering the content?
how can I make the last column filling out the rest of the listView?
You can set the TextAlign of all but the 1st Column's Header; it is always left aligned. To change that you need to owner draw it.
There is no automatic filling option so you need to write a setColumnwidth function, that loops over all but the last columns and sums their Widths; then it subtract the sum from the ListView's Clientsize.Width and set the last column's Width.
The display bug in the gridlines is new to me; so far I don't know how to fix it; maybe owner-drawing will help there as well..?
Update:
Here is some code:
void setLastColumnTofill(ListView lv)
{
int sum = 0;
int count = lv.Columns.Count;
for (int i = 0; i < count - 1; i++) sum += lv.Columns[i].Width;
lv.Columns[count - 1].Width = lv.ClientSize.Width - sum;
}
After setting OwnerDraw = true you could code the three (all are needed!) Draw event :
private void listView1_DrawColumnHeader(object sender, DrawListViewColumnHeaderEventArgs e)
{
e.Graphics.FillRectangle(SystemBrushes.Menu, e.Bounds);
e.Graphics.DrawRectangle(SystemPens.GradientInactiveCaption,
new Rectangle(e.Bounds.X , 0, e.Bounds.Width , e.Bounds.Height) );
string text = listView1.Columns[e.ColumnIndex].Text;
TextFormatFlags cFlag = TextFormatFlags.HorizontalCenter
| TextFormatFlags.VerticalCenter;
TextRenderer.DrawText(e.Graphics, text, listView1.Font, e.Bounds, Color.Black, cFlag);
}
private void listView1_DrawItem(object sender, DrawListViewItemEventArgs e)
{
e.DrawDefault = true;
}
private void listView1_DrawSubItem(object sender, DrawListViewSubItemEventArgs e)
{
e.DrawDefault = true;
}
You may want to play a little with the colors or the widths..
If you have an ImageList containing images for displaying the sort order (or other things) you can add this to draw them as well:
ColumnHeader colH = listView1.Columns[e.ColumnIndex];
int ii = colH.ImageIndex;
if (ii >= 0 && ii < imageList1.Images.Count)
e.Graphics.DrawImage(imageList1.Images[ii],
e.Bounds.Width + e.Bounds.X - imageList1.ImageSize.Width, 0);
After setting OwnerDraw to true you can do other stuff like this:
Draw ListView
int sortIndex = 0;
private void listView1_DrawColumnHeader(object sender,
DrawListViewColumnHeaderEventArgs e)
{
var state = e.State == ListViewItemStates.Selected ?
VisualStyleElement.Header.Item.Hot : VisualStyleElement.Header.Item.Normal;
var sortOrder = listView1.Sorting == SortOrder.Ascending ?
VisualStyleElement.Header.SortArrow.SortedUp :
VisualStyleElement.Header.SortArrow.SortedDown;
var itemRenderer = new VisualStyleRenderer(state);
var sortRenderer = new VisualStyleRenderer(sortOrder);
var r = e.Bounds;
r.X += 1;
itemRenderer.DrawBackground(e.Graphics, r);
r.Inflate(-2, 0);
var flags = TextFormatFlags.Left | TextFormatFlags.VerticalCenter |
TextFormatFlags.SingleLine;
itemRenderer.DrawText(e.Graphics, r, e.Header.Text, false, flags);
var d = SystemInformation.VerticalScrollBarWidth;
if (e.ColumnIndex == sortIndex) //Sorted Column
sortRenderer.DrawBackground(e.Graphics,
new Rectangle(r.Right - d, r.Top, d, r.Height));
}
private void listView1_DrawItem(object sender, DrawListViewItemEventArgs e)
{
e.DrawDefault = true; /*Use default rendering*/
}
private void listView1_DrawSubItem(object sender, DrawListViewSubItemEventArgs e)
{
e.DrawDefault = true; /*Use default rendering*/
}
Fill ListView using Last Column
private void ListViewSample_Load(object sender, EventArgs e)
{
var otherItemWisth= this.listView1.Columns.Cast<ColumnHeader>()
.Where(x => x.Index < this.listView1.Columns.Count - 1)
.Sum(x => x.Width);
this.listView1.Columns[this.listView1.Columns.Count - 1].Width =
this.listView1.ClientSize.Width - otherItemWisth;
}
Result

ListView - Align vertical grid lines with Headers dividers - Make last Column fill the space

I am trying to write a Windows Forms MusicPlayer application in C#.
The application should show a list and have some play / stop buttons.
I just started half an hour ago, but my design is almost finished. Now I got 3 things to fix. A bug and 2 good looking things:
on the picture you can see the bug I've found. You might say that's nothing, but its a eye catcher. How can I fix this?
how can I align center the headline of a column, without centering the content?
how can I make the last column filling out the rest of the listView?
You can set the TextAlign of all but the 1st Column's Header; it is always left aligned. To change that you need to owner draw it.
There is no automatic filling option so you need to write a setColumnwidth function, that loops over all but the last columns and sums their Widths; then it subtract the sum from the ListView's Clientsize.Width and set the last column's Width.
The display bug in the gridlines is new to me; so far I don't know how to fix it; maybe owner-drawing will help there as well..?
Update:
Here is some code:
void setLastColumnTofill(ListView lv)
{
int sum = 0;
int count = lv.Columns.Count;
for (int i = 0; i < count - 1; i++) sum += lv.Columns[i].Width;
lv.Columns[count - 1].Width = lv.ClientSize.Width - sum;
}
After setting OwnerDraw = true you could code the three (all are needed!) Draw event :
private void listView1_DrawColumnHeader(object sender, DrawListViewColumnHeaderEventArgs e)
{
e.Graphics.FillRectangle(SystemBrushes.Menu, e.Bounds);
e.Graphics.DrawRectangle(SystemPens.GradientInactiveCaption,
new Rectangle(e.Bounds.X , 0, e.Bounds.Width , e.Bounds.Height) );
string text = listView1.Columns[e.ColumnIndex].Text;
TextFormatFlags cFlag = TextFormatFlags.HorizontalCenter
| TextFormatFlags.VerticalCenter;
TextRenderer.DrawText(e.Graphics, text, listView1.Font, e.Bounds, Color.Black, cFlag);
}
private void listView1_DrawItem(object sender, DrawListViewItemEventArgs e)
{
e.DrawDefault = true;
}
private void listView1_DrawSubItem(object sender, DrawListViewSubItemEventArgs e)
{
e.DrawDefault = true;
}
You may want to play a little with the colors or the widths..
If you have an ImageList containing images for displaying the sort order (or other things) you can add this to draw them as well:
ColumnHeader colH = listView1.Columns[e.ColumnIndex];
int ii = colH.ImageIndex;
if (ii >= 0 && ii < imageList1.Images.Count)
e.Graphics.DrawImage(imageList1.Images[ii],
e.Bounds.Width + e.Bounds.X - imageList1.ImageSize.Width, 0);
After setting OwnerDraw to true you can do other stuff like this:
Draw ListView
int sortIndex = 0;
private void listView1_DrawColumnHeader(object sender,
DrawListViewColumnHeaderEventArgs e)
{
var state = e.State == ListViewItemStates.Selected ?
VisualStyleElement.Header.Item.Hot : VisualStyleElement.Header.Item.Normal;
var sortOrder = listView1.Sorting == SortOrder.Ascending ?
VisualStyleElement.Header.SortArrow.SortedUp :
VisualStyleElement.Header.SortArrow.SortedDown;
var itemRenderer = new VisualStyleRenderer(state);
var sortRenderer = new VisualStyleRenderer(sortOrder);
var r = e.Bounds;
r.X += 1;
itemRenderer.DrawBackground(e.Graphics, r);
r.Inflate(-2, 0);
var flags = TextFormatFlags.Left | TextFormatFlags.VerticalCenter |
TextFormatFlags.SingleLine;
itemRenderer.DrawText(e.Graphics, r, e.Header.Text, false, flags);
var d = SystemInformation.VerticalScrollBarWidth;
if (e.ColumnIndex == sortIndex) //Sorted Column
sortRenderer.DrawBackground(e.Graphics,
new Rectangle(r.Right - d, r.Top, d, r.Height));
}
private void listView1_DrawItem(object sender, DrawListViewItemEventArgs e)
{
e.DrawDefault = true; /*Use default rendering*/
}
private void listView1_DrawSubItem(object sender, DrawListViewSubItemEventArgs e)
{
e.DrawDefault = true; /*Use default rendering*/
}
Fill ListView using Last Column
private void ListViewSample_Load(object sender, EventArgs e)
{
var otherItemWisth= this.listView1.Columns.Cast<ColumnHeader>()
.Where(x => x.Index < this.listView1.Columns.Count - 1)
.Sum(x => x.Width);
this.listView1.Columns[this.listView1.Columns.Count - 1].Width =
this.listView1.ClientSize.Width - otherItemWisth;
}
Result

C# Winform multiple image in a single cell DataGirdViewImageColumn

I want add multiple image into one cell in DataGirdViewImageColumn
You can easily seen five circles with different color, at first, I've tried to written "•" character in DataGirdViewTextBoxColumn but I can not change color for each character, does anyone have any idea to do this?
Many thanks
Here is an example of cell-painting this one Cell:
The CellPainting event does the work:
private void dataGridView1_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
{
if (e.ColumnIndex == 2 && e.RowIndex >= 0 && e.Value != null)
{
// use your own code here...
string val = ((int)e.Value).ToString("00000000");
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.PaintBackground(e.CellBounds, false);
for (int i = 0; i < count; i++)
using (SolidBrush brush = new // ..and here!!!
SolidBrush(colors[Convert.ToInt16(val[i].ToString())]))
e.Graphics.FillEllipse(brush,
e.CellBounds.X + i * 12 + 6, e.CellBounds.Y + 5 , 11, 11);
e.Handled = true;
}
}
It makes use of a predefined list of colors:
List<Color> colors = new List<Color>() // use your own set of colors here!
{ Color.Red, Color.Black, Color.Blue, Color.ForestGreen, Color.DarkKhaki,
Color.Goldenrod, Color.DeepPink, Color.Orange, Color.DarkSlateGray, Color.GreenYellow };
And a count of how many dots to draw:
int count = 7; // ditto!
Obviously you may want to change the way I stored the data for my test...
I have stored a large integer number as the cell's value and each digint is mapped onto one color.
you can (and probably should) change this!
Here is how I set up may DGV:
dataGridView1.Columns.Add("asd", "asd");
dataGridView1.Columns.Add("ko", "ok");
dataGridView1.Columns.Add("col", "col");
dataGridView1.Rows.Add(66);
for (int r = 0; r < 66; r++)
{
dataGridView1[0, r].Value = r;
dataGridView1[1, r].Value = r * 3.14f;
dataGridView1[2, r].Value = (int )( r * 314.345 + 1231542f);
}
Obviously none of it is how you will do it ;-)
There are different approaches to this. Here are the three I'd consider:
Draw the cell by yourself in the CellPainting event (see stackoverflow)
Draw an image by yourself and assign it to the image column (see stackoverflow)
Create an own column type with an own editor (see msdn) which cares for the drawing
But in all the three options you will have to draw the circles with GDI+, so they are pretty close to each other.
I'd go for option 3 because it seems to be the cleanest approach to me and the column can be reused in other grids easily. With "clean" I mean the code to draw the circles is located in the column implementation instead of the form (or any other place) where you are reacting on the grid's event.

Flickering Tab Control with MouseMove Determining What To Draw

I have been researching this all day, (Go ahead and laugh lol) and I don't see any solutions to the age old Forms problem of flickering controls. My control is a TabControl and I am using DrawMode OwnerDrawFixed. I am hooking the following events. In short I am creating a TabControl with closable "X" buttons that are 12x12 png resources. The close buttons are all gray but if I mouse over one it should use a different image (a red X).
MouseDown: Loops all TabPages and checks if I have clicked on a rectangle where I am drawing my close button image.
MouseLeave: I need to Invalidate when I leave the TabControl to ensure everything is drawn correctly
MouseMove: Loops all TabPages and checks if I have moused over a rectangle where I am drawing my close button image. If I am mousing over then I save the tab page index so my paint can change the image used for the close button.
DrawItem: Here I simply draw the image
Things I have tested but no luck...
Making my own TabControl class which inherits TabControl and in the constructor I SetStyles for OptimizedDoubleBuffering To true (I set the other suggested flags to true)
I tried overriding CreateParams so I could or this value... createParams.ExStyle |= 0x00000020; (I have no idea what this does but read a user suggested to do this.
Setting the form DoubleBuffered (does nothing)
Anyways, I can't think what to do and I have read about this for awhile.
Here is my code for all events. I just want to have close buttons on my tabs that get highlighted when I mouse over them. Thanks.
private int mousedOver = -1;//indicates which close button is moused over
private void tabControl_DrawItem(object sender, DrawItemEventArgs e)
{
e.Graphics.DrawImage(e.Index == mousedOver ? Resources.redX : Resources.grayX, e.Bounds.Right - 15, e.Bounds.Top + 4);
}
private void tabControl_MouseDown(object sender, MouseEventArgs e)
{
TabControl tc = sender as TabControl;
if (tc.TabCount == 1) return;
for (int i = 0; i < tc.TabPages.Count; i++)
{
Rectangle r = tc.GetTabRect(i);
Rectangle closeButton = new Rectangle(r.Right - 15, r.Top + 4, 12, 12);
if (closeButton.Contains(e.Location))
{
TabPage tp = tc.TabPages[i];
tc.TabPages.Remove(tp);
tp.Dispose();
break;
}
}
}
private void tabControl_MouseMove(object sender, MouseEventArgs e)
{
TabControl tc = sender as TabControl;
for (int i = 0; i < tc.TabPages.Count; i++)
{
Rectangle r = tc.GetTabRect(i);
Rectangle closeButton = new Rectangle(r.Right - 15, r.Top + 4, 12, 12);
if (closeButton.Contains(e.Location))
{
mousedOver = i;
tc.Invalidate();
return;
}
}
mousedOver = -1;
tc.Invalidate();
}
private void tabControl_MouseLeave(object sender, EventArgs e)
{
TabControl tc = sender as TabControl;
mousedOver = -1;
tc.Invalidate();
}
It does look like you are invalidating too often. Try filtering it so that you only invalidate it when the control needs to be re-painted:
private void tabControl_MouseMove(object sender, MouseEventArgs e) {
TabControl tc = sender as TabControl;
for (int i = 0; i < tc.TabPages.Count; i++) {
Rectangle r = tc.GetTabRect(i);
Rectangle closeButton = new Rectangle(r.Right - 15, r.Top + 4, 12, 12);
if (closeButton.Contains(e.Location)) {
if (mousedOver != i) {
mousedOver = i;
tc.Invalidate(r);
}
} else if (mousedOver == i) {
int oldMouse = mousedOver;
mousedOver = -1;
tc.Invalidate(tc.GetTabRect(oldMouse));
}
}
}
I would keep the CreateParams override, but as a native windows control, you can probably never totally eliminate some flicker.
You could also try setting the DoubleBuffered property of the control, by doing
Control.DoubleBuffered = true;
I know that this works with DataGridViews, ListViews, Forms and Panels.
Documentation can be found on MSDN.

Vertical text in datagridview

I want to show the text in the header cells in vertical orientation. How can I do it?
Thanks
You can achieve the result you want using custom cell painting for the header.
In answer to your comment asking for a way to align the text with the bottom of the cell, I've added comments to my code. They are hopefully clear.
You need the following code (say in the Form_Load after initializing components)
dataGridView1.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.EnableResizing;
dataGridView1.ColumnHeadersHeight = 50;
dataGridView1.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCellsExceptHeader;
// Here we attach an event handler to the cell painting event
dataGridView1.CellPainting += new DataGridViewCellPaintingEventHandler(dataGridView1_CellPainting);
Next you need something like the following code:
void dataGridView1_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
{
// check that we are in a header cell!
if (e.RowIndex == -1 && e.ColumnIndex >= 0)
{
e.PaintBackground(e.ClipBounds, true);
Rectangle rect = this.dataGridView1.GetColumnDisplayRectangle(e.ColumnIndex, true);
Size titleSize = TextRenderer.MeasureText(e.Value.ToString(), e.CellStyle.Font);
if (this.dataGridView1.ColumnHeadersHeight < titleSize.Width)
{
this.dataGridView1.ColumnHeadersHeight = titleSize.Width;
}
e.Graphics.TranslateTransform(0, titleSize.Width);
e.Graphics.RotateTransform(-90.0F);
// This is the key line for bottom alignment - we adjust the PointF based on the
// ColumnHeadersHeight minus the current text width. ColumnHeadersHeight is the
// maximum of all the columns since we paint cells twice - though this fact
// may not be true in all usages!
e.Graphics.DrawString(e.Value.ToString(), this.Font, Brushes.Black, new PointF(rect.Y - (dataGridView1.ColumnHeadersHeight - titleSize.Width) , rect.X));
// The old line for comparison
//e.Graphics.DrawString(e.Value.ToString(), this.Font, Brushes.Black, new PointF(rect.Y, rect.X));
e.Graphics.RotateTransform(90.0F);
e.Graphics.TranslateTransform(0, -titleSize.Width);
e.Handled = true;
}
}
A simpler and more effective renderer
Attach event either through designer, or with this line of code
dataGridView1.CellPainting += new DataGridView1_CellPainting(dataGridView1_CellPainting);
Event handler to draw rotated text
private void DataGridView1_CellPainting(object sender, DataGridViewCellPaintingEventArgs e) {
// Vertical text from column 0, or adjust below, if first column(s) to be skipped
if (e.RowIndex == -1 && e.ColumnIndex >= 0) {
e.PaintBackground(e.CellBounds, true);
e.Graphics.TranslateTransform(e.CellBounds.Left , e.CellBounds.Bottom);
e.Graphics.RotateTransform(270);
e.Graphics.DrawString(e.FormattedValue.ToString(),e.CellStyle.Font,Brushes.Black,5,5);
e.Graphics.ResetTransform();
e.Handled = true;
}
}
DataGrid d = new DataGrid();
d.Columns[0].HeaderStyle.VerticalAlign = VerticalAlign.Bottom;
If you are looking for gridview then you case like this :-
GridView gv = new GridView ();
gv.Columns[0].ItemStyle.VerticalAlign = VerticalAlign.Bottom;
If you are looking for datagridview then you case like this :-
Create an object of datagridview.
gv.Columns["ColumnName"].HeaderCell.Style.Alignment = DataGridViewContentAlignment.BottomCenter;
void dataGridView1_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
{
if (e.RowIndex == -1 && e.ColumnIndex >= 0)
{
e.PaintBackground(e.ClipBounds, true);
Rectangle rect =
this.dataGridView1.GetColumnDisplayRectangle(e.ColumnIndex, true);
Size titleSize =
TextRenderer.MeasureText(e.Value.ToString(), e.CellStyle.Font);
if (this.dataGridView1.ColumnHeadersHeight <
titleSize.Width)
this.dataGridView1.ColumnHeadersHeight =
titleSize.Width;
e.Graphics.TranslateTransform(0, titleSize.Width);
e.Graphics.RotateTransform(-90.0F);
e.Graphics.DrawString(e.Value.ToString(), this.Font,
Brushes.Orange, new PointF(rect.Y, rect.X));
e.Graphics.RotateTransform(90.0F);
e.Graphics.TranslateTransform(0, -titleSize.Width);
e.Handled = true;
}
}
In addition, you could set the AutoSizeColumnsMode property of the
DataGridView to AllCellsExceptHeader in order to make the DataGridView
compact.

Categories