C# User generated Label, that can be selected and its properties changed - c#

I am trying to develop a software for designing and generating batches of IDs for schools etc. On the design part, a user would be able to create any number of text fields, select them, drag them around and/or nudge them with the arrow keys and edit their properties like text, font, size, and location. What I am trying to achieve is a much simpler version of the Visual Studio form designer. Here is a screenshot of my UI
So far, I have created a User Control called 'UserLabel' that inherits the Label class (I like Label because I can set the background transparent). A new UserLabel can be created by clicking the 'Add Text' button and can be dragged around the picture box.
public partial class UserLabel : Label
{
private System.Drawing.Point StartPoint;
private bool IsMouseDown = false;
public UserLabel()
{
InitializeComponent();
this.Text = "New Item";
this.AutoSize = true;
this.BackColor = System.Drawing.Color.Transparent;
this.Font = new System.Drawing.Font("Arial", 12);
this.Location = new System.Drawing.Point(5, 5);
this.Size = new System.Drawing.Size(50, 20);
}
protected override void OnMouseDown(System.Windows.Forms.MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
IsMouseDown = true;
this.BringToFront();
StartPoint = e.Location;
}
}
protected override void OnMouseUp(System.Windows.Forms.MouseEventArgs e)
{
IsMouseDown = false;
}
protected override void OnMouseMove(System.Windows.Forms.MouseEventArgs e)
{
if (IsMouseDown)
{
this.Left = e.X + this.Left - StartPoint.X;
this.Top = e.Y + this.Top - StartPoint.Y;
}
}
}
But since Label is not selectable, I have no clue how to proceed from here. I have tried adding a BorderStyle when the item is selected (see below) to give visual cues to the user which item is selected. But this does not achieve anything as far as being able to edit the properties. And I couldn't even manage to remove the BorderSyle to indicate a loss of focus.
protected override void OnMouseDown(System.Windows.Forms.MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
IsMouseDown = true;
this.BringToFront();
StartPoint = e.Location;
this.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
}
}
Can anyone please point me in the right direction as to how I can override the CanFocus property (or Selectable property, I have no clue) or any other suggestion on how to achieve this? I am also open to any other approach to achieve my goal.
NOTE: I have tried a different approach where there will be a set number of items that are hidden and can be set visible by user like this. But I don't like this as it is repetitive and even though 10 items seem plenty, I can never know if a user might need more than this.

You do not have to hide/show any static number of rows, it is a bad design choice. All you need is use a DataGridView and a Button to add rows to the DataGridView. This way users can type anything they want into your textboxes and select from combobox of the row whatever they want to choose, also use a submit button to push code to db/render it on ui wherever you want to. Sample code might look like this-
private void button1_Click(object sender, EventArgs e)
{
DataGridViewColumn dataGridViewColumn = new DataGridViewColumn(new DataGridViewTextBoxCell()) ;
this.dataGridView1.Columns.Add(dataGridViewColumn);
DataGridViewColumn dataGridViewColumn2 = new DataGridViewColumn(new DataGridViewComboBoxCell());
this.dataGridView1.Columns.Add(dataGridViewColumn2);
DataGridViewRow dataGridViewRow = new DataGridViewRow();
dataGridViewRow.Cells.Add(new DataGridViewTextBoxCell());
this.dataGridView1.Rows.Add(new DataGridViewRow());
}

Related

C# change table row color when mouse hover

I have a table layout panel in my winform, and I want to add an effect to the rows whenever the mouse is hover a row.
I think I need to make a Mouse_over action over the table, and then detect the row number of the table, and then iterate on each cell on the row and change it's back color.
The problem is that I don't know how to get the row number.
Any ideas please?
EDIT: I am adding rows to the table dynamically, I have set of buttons and when I click one it deletes all old rows from the table and adds new rows that are related to this button.
This is the way I add new rows:
tlp.RowCount++;
tlp.RowStyles.Add(new RowStyle(SizeType.AutoSize));
tlp.Controls.Add(new Label() { ... }, cellIDX, rowIDX);
// adding more columns //
and to remove old rows I loop through all rows from bottom to top, removes all related controls of current cell, then I remove style and row num like so:
tlp.RowStyle.RemoveAt(rowNum);
tlp.RowCount--;
Here is what you can do:
As there actually are no Cells in a TableLayouPanel all you can do is
detect where th mouse is
paint the TLP in the CellPaint event.
Since your TLP most likely will contain controls they also need to detect whether the mouse is on them..
Here is an example:
First a class level variable to store the current row:
int tlpRow = -1;
Next a CellPaint event that can color a row:
private void tableLayoutPanel1_CellPaint(object sender, TableLayoutCellPaintEventArgs e)
{
if (e.Row == tlpRow)
using (SolidBrush brush = new SolidBrush(Color.FromArgb(123, 234, 45, 67)))
e.Graphics.FillRectangle(brush, e.CellBounds);
}
Next we need detection routines. First one for the TLP:
bool testTLP(TableLayoutPanel tlp, Point pt)
{
var rs = tableLayoutPanel1.RowStyles;
var rh = 0f;
for (int i = 0; i < rs.Count; i++)
{
if (pt.Y > rh && pt.Y <= rh + rs[i].Height )
{
if (tlpRow != i)
{
tlpRow = i;
tableLayoutPanel1.Invalidate();
return true;
}
}
rh += rs[i].Height;
}
tlpRow = -1;
return false;
}
It loops over all rows and adds up the heights until it has found the right one. Then it stores the row index and triggers the CellPaint event.
We can use the same routine for the controls:
bool testTLP(TableLayoutPanel tlp)
{
Point point = tlp.PointToClient(Control.MousePosition);
return testTLP(tlp, point);
}
We simply calculate the mouse position relative to the TLP and call the same test.
Note that this test only for 1 level of nesting. If you have deeper nested control you may need to expand on the test somewhat..!
We also need to call the tests; the TLP test can be called in the MouseMove:
private void tableLayoutPanel1_MouseMove(object sender, MouseEventArgs e)
{
testTLP(tableLayoutPanel1, e.Location);
}
The controls get hooked up all together maybe like this:
void hookUpControls(TableLayoutPanel tlp)
{
foreach (Control ctl in tlp.Controls)
{
ctl.MouseMove += (s, e) => { testTLP(tlp); };
}
}
I use the MouseMove event as the MouseEnter sometimes slipped through in my tests..
If you add controls later you need to hook the up as well. Make sure not to hook one up multiple times!
Most likely you want to reset the coloring when leaving the TLP:
private void tableLayoutPanel1_MouseLeave(object sender, EventArgs e)
{
Point tplPoint = tableLayoutPanel1.PointToClient(Control.MousePosition);
if (!tableLayoutPanel1.ClientRectangle.Contains(tplPoint)) tlpRow = -1;
tableLayoutPanel1.Invalidate();
}
Result:
Note: when you add Controls dynamically you need to hook the up as well. Here is an example:
Label lbl = new Label() { Text = "newbie" };
lbl.MouseMove += (ss, ee) => { testTLP(tlp, lbl); };
tlp.Controls.Add(lbl, cellIDX, rowIDX);
If you find the coloring flickers you can simply add a DoubleBuffered subclass:
class DoubleBufferedTLP : TableLayoutPanel
{
public DoubleBufferedTLP()
{
DoubleBuffered = true;
}
}
To do so you need to add to the project, compile, check to see it appears in the ToolBox. If you want to you can simply change the two sponts in the form_designer class..

Add dropdown for a specific cell in a datagridview

I am trying to add different controls to cells in the same column. The drop down does not show and there is no visible setter:
private void AddBooleanDropDown(DataGridView grid, int row, KeyValuePair<string, string> kvp)
{
DataGridViewComboBoxCell dropDownCell = new DataGridViewComboBoxCell();
dropDownCell.DataSource = new string[] { "True", "False" };
grid.Rows[row].Cells["Value"] = dropDownCell;
}
Not sure if this will be helpful to you, but maybe an alternative method?
I wanted to be able to update an excel spreadsheet that I read into a DataGridView and give the user a few options. I used a ContextMenuStrip that would display on a MouseClick event.
It displays a small menu when you right click on a cell:
If it's not what you're looking for at all, sorry; just perhaps an alternate solution:
////////////////////////////////////////////////////////////////////////
//Change Priority Strip
////////////////////////////////////////////////////////////////////////
ContextMenuStrip changePriority = new ContextMenuStrip();
ToolStripMenuItem highPriority = new ToolStripMenuItem("High Priority");
changePriority.Items.Add(highPriority);
highPriority.Click += new EventHandler(changePriorityHighEvent);
ToolStripMenuItem normalPriority = new ToolStripMenuItem("Normal Priority");
changePriority.Items.Add(normalPriority);
normalPriority.Click += new EventHandler(changePriorityNormalEvent);
ToolStripMenuItem lowPriority = new ToolStripMenuItem("Low Priority");
changePriority.Items.Add(lowPriority);
lowPriority.Click += new EventHandler(changePriorityLowEvent);
////////////////////////////////////////////////////////////////////////
private void gridView_CellMouseClick(object sender, DataGridViewCellMouseEventArgs e)
{
if (e.Button == MouseButtons.Right) //On Right Click
{
DataGridView.HitTestInfo hit = gridView.HitTest(e.X, e.Y); //Get the clicked cell
if (e.RowIndex < 0) //If it's a header, ignore
return;
gridView.CurrentCell = gridView[e.ColumnIndex, e.RowIndex]; //Select the cell for future info
if (gridView.CurrentCell.ColumnIndex == 6) //If this is the priority column
{
changePriority.Show(Cursor.Position.X, Cursor.Position.Y); //Show the strip
}
}
}
private void changePriorityHighEvent(Object sender, EventArgs e) {
//make changes
}
private void changePriorityNormalEvent(Object sender, EventArgs e) {
//make changes
}
private void changePriorityLowEvent(Object sender, EventArgs e) {
//make changes
}
Here is very good MSDN Example.
The DataGridView control provides several column types, enabling your users to enter and edit values in a variety of ways. If these column types do not meet your data-entry needs, however, you can create your own column types with cells that host controls of your choosing. To do this, you must define classes that derive from DataGridViewColumn and DataGridViewCell. You must also define a class that derives from Control and implements the IDataGridViewEditingControl interface.
Not sure if you can change a specific cell in a grid unless it's the same type.
You could try adding a new column of combo boxes all with that data source
var newCol = new DataGridViewComboBoxColumn()
{
DataSource = new string[] { "True", "False" }
};
grid.Columns.Add(newCol);
also you might want to check that the int your passing in isn't greater than the number of rows.

show all columns in datagridview

Basically I have the following code to bring up 3 different datagrid views depending on the button clicked.
public partial class ChooseDB : Form
{
private DataGridView doctorsDataGridView = new DataGridView();
private DataGridView patientsDataGridView = new DataGridView();
private DataGridView hospitalsDataGridView = new DataGridView();
public ChooseDB()
{
InitializeComponent();
}
public void buttonDoctorsDB_Click(object sender, EventArgs e)
{
doctorsDataGridView.DataSource = doctorsDataSet.Doctors;
doctorsDataGridView.Dock = DockStyle.Right;
if (Controls.Contains(patientsDataGridView))
Controls.Remove(patientsDataGridView);
if (Controls.Contains(hospitalsDataGridView))
Controls.Remove(hospitalsDataGridView);
this.Controls.Add(doctorsDataGridView);
}
public void buttonPatientsDB_Click(object sender, EventArgs e)
{
patientsDataGridView.DataSource = patientsDataSet.Patients;
patientsDataGridView.Dock = DockStyle.Right;
if (Controls.Contains(doctorsDataGridView))
Controls.Remove(doctorsDataGridView);
if (Controls.Contains(hospitalsDataGridView))
Controls.Remove(hospitalsDataGridView);
this.Controls.Add(patientsDataGridView);
}
public void buttonHospitalsDB_Click(object sender, EventArgs e)
{
hospitalsDataGridView.DataSource = hospitalsDataSet.Hospitals;
hospitalsDataGridView.Dock = DockStyle.Right;
if (Controls.Contains(patientsDataGridView))
Controls.Remove(patientsDataGridView);
if (Controls.Contains(doctorsDataGridView))
Controls.Remove(doctorsDataGridView);
this.Controls.Add(hospitalsDataGridView);
}
}
}
So far so good, but the table that it brings up is all "smooshed" to the right
I've checked up on autosizing but haven't found how to apply it to my code specifically.
I would like it to show all the columns, instead of just showing the first two and then giving a scrollbar at tha bootom.
I'm assuming here that autosizing is the right way to go, if not please set me on the right path.
Autosizing of a dataGridView should be on by default (and its probably set to 'displayed cells'. Does your first view of a dataGrid look fine (with all 3 columns showing up), but its subsequent views that appear scrunched?
In one of my applications, I handle the autosizing within the form1_load event.
private void Form1_Load(object sender, EventArgs e)
{
GetICD10();
FreezeBand(dataGridView1.Columns[0]); // Client requested to have ICD code column "frozen" by default
// Cannot seem to select both autosize and allow user to size in designer, so below is the "code around".
// Designer has autosize set to displayedcells.
dataGridView1.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.None; // Turn off autosize
dataGridView1.AllowUserToResizeRows = true; // Turn on letting user size columns
dataGridView1.AllowUserToOrderColumns = true;
// Create tooltip and populate it
var toolTip1 = new ToolTip { AutoPopDelay = 5000, InitialDelay = 1000, ReshowDelay = 500, ShowAlways = true };
toolTip1.SetToolTip(tbCode, "Enter an ICD code to search for");
toolTip1.SetToolTip(tbDescriptionLong, "Enter a description to search for");
}

Add dynamic labels and textboxes from a checkedListBox

I have a checkedListBox, that contains names of some chargingstations i have in a SQL database. I have filled the checkedListBox with the station names, and i would like that if you check an item in the box, a label with the text "Km to "+itemsChecked.StationName would appear just below the checkedListbox, as well as a textBox, where one would enter the kilometers to the station.
It's being used to create a new charging station, and the kilometers are the cost of the edge to the next station.
I've tried something like this:
private void stationCheckedListBox_SelectedIndexChanged(object sender, EventArgs e)
{
foreach (BetterServiceReference.Station itemsChecked in stationCheckedListBox.CheckedItems)
{
var lbl = new Label();
lbl.Name = "lblAuto" + itemsChecked.StationId;
lbl.Text = "Km to " + itemsChecked.StationName;
lbl.AutoSize = true;
lbl.Location = new Point(33, 462);
lbl.Name = "label1";
lbl.Size = new Size(35, 13);
tabPage7.Controls.Add(lbl);
}
Only it doesn't actually create a label.
I created a sample program, and it seems to work fine for me:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void checkedListBox1_SelectedIndexChanged(object sender, EventArgs e)
{
foreach (var item in checkedListBox1.CheckedItems)
{
Label lbl = new Label();
lbl.Text = "Testing";
lbl.Location = new Point(125, 125);
this.Controls.Add(lbl);
}
}
}
Some things to check / try:
Make sure the location of your label is not behind some other control!!
Use var / object instead of "BetterServiceReference.Station"
Is tabPage7 the name of your form? Try using this.Controls.Add(lbl);
I will also warn you that your function will not remove the labels once an item is unchecked. Also, as-is, it will only create one label in one spot for any checkbox checked. It's going to take a bit of work to implement what you are trying to do; as the other answer suggested, you may be better off toggling controls between visible / invisible.
But, hopefully my answer will help you dynamically create a label, and creating the textboxes will be very similar.
I think you need to create the label in the designer and make it Visible="False". Then in your code, make it Visible="True" as needed. Same with the textbox.

Data binding to list and dynamically adding controls using Windows Forms

I have a List<Appointment> where an Appointment is.
public class Appointment
{
public string Title { get; set; }
public string Start { get; set; }
public string End { get; set; }
}
I want to dynamically add each list item on a separate line on the form like so:
item.Title + " between" + item.Start + " and " + item.End;
I want to be able to click each item (the text), then with each click it can toggle the colour of the text between red and black (that is, if black it turns red, if red it turns black when you click).
I come from a web background, but I am just struggling with Windows Forms data binding. I've tried with table layout panel but just don't know where to begin with changing the color of an item on click.
PS: If it helps, the number of items in the list will probably not be more than 10.
I've gotten a bit further as per Jamie Ide's comment:
var appts = GetAllCalendarItems();
foreach (var item in appts)
{
Label label = new Label();
label.Text = item.Title + " between" + item.Start + " and " + item.End;
label.Click += new EventHandler(label_Click);
flowLayoutPanel1.Controls.Add(label);
}
...
private void label_Click(object sender, EventArgs e)
{
// This is wrong - what goes here??
((Label)sender).ForeColor = Color.Red;
}
Dynamically laying out Windows Forms is a huge pain. I don't have time to code this but the steps are:
Add FlowLayoutPanel to form as a container
Look through your Appointments and create label controls for each
Add the label controls to the panel's Controls collection
Assign an OnClick handler to each label control to toggle the color
Don't bother with databinding for this.
If you haven't changed the label's initial color from the default, this will toggle it:
private void label1_Click(object sender, EventArgs e)
{
var lbl = (Label)sender;
var toggle = lbl.ForeColor == SystemColors.ControlText;
lbl.ForeColor = toggle ? Color.Red : SystemColors.ControlText;
}
You could add each text field as a member of a Label or List view item. Then handle the "OnClick" or "SelectedIndexChanged" event. To create an an OnClick event handler double click on the control in the design view. Edit the handler like this:
private void listView1_SelectedIndexChanged(object sender, EventArgs e)
{
listView1.SelectedItems[0].ForeColor = Color.Red;
}
If you are unsure about event handlers, don't be put off they are quite easy, just look them up here perhaps. If the list view is not what you are looking for, try the same approach on a different control.
I guess I can't comment on Jamie's answer, but raklos you can programmatically add the OnClick method by doing:
label.Click += new EventHandler(label_Click);
Visual Studio should auto-generate the stubs for you when you start typing that out.
Something like this could get you started:
private void label_Click(object sender, EventArgs e) { ToggleTextColor((Label)sender); }
private void ToggleTextColor(Control control)
{
var currentColor = control.ForeColor;
control.ForeColor = currentColor == Color.Red ? Color.Black : Color.Red;
}
You can cheat and make create it in a WebBrowserControl.
You will be in familiar ground.
Use ObjectforScripting for WeBbrowser <=> winforms communication.
http://msdn.microsoft.com/en-us/library/system.windows.forms.webbrowser.objectforscripting.aspx

Categories