I have no easy way to explain this but I have 100 buttons, written up to be in an array of buttons with this here line of code:
static array <Button^, 2>^ button = gcnew array <Button^, 2> (10,10);
And each one is initialized following this suit:
button[4,0] = button40;
I also have one event handler for all of these buttons. What I need to know is what is a way I can determine which button has been clicked for example if you click the button in the third row and the fourth column, it should know that button named button23 (saved in array as button[2,3]) has been pressed.
Also another thing, this is C++/CLI and I'm aware of how weird this code is.
In your event handler you have the sender of the event:
void button_Click(Object^ sender, EventArgs^ e)
{
// This is the name of the button
String^ buttonName = safe_cast<Button^>(sender)->Name;
}
If you need item's indices (row and column) you need to loop through array because Array::IndexOf doesn't support multidimensional arrays. Let's write (somewhere) a generic function like this:
static void Somewhere::IndexOf(Array^ matrix, Object^ element, int% row, int% column)
{
row = column = -1;
for (int i=matrix->GetLowerBound(0); i <= matrix->GetUpperBound(0); ++i)
{
for (int i=matrix->GetLowerBound(1); i <= matrix->GetUpperBound(1); ++i)
{
// Note reference comparison, this won't work with boxed value types
if (Object::ReferenceEquals(matrix->GetValue(i, j), element)
{
row = i;
column = j;
return;
}
}
}
}
So finally you may have this:
void button_Click(Object^ sender, EventArgs^ e)
{
// This is the name of the button
String^ buttonName = safe_cast<Button^>(sender)->Name;
// This is the "location" of the button
int row = 0, column = 0;
Somewhere::IndexOf(button, sender, row, column);
}
Related
I'm trying to implement a timer that runs every 10ms to determine if a field has been filled by the user. Essentially if a textbox (up to 10) is filled, a button will enable. If they fill, then delete the input, the timer will determine that and disable the button. That can be seen in the following:
private: System::Void StartBtn_Tick(System::Object^ sender, System::EventArgs^ e) {
int count = 0;
array < TextBox ^ > ^ textbox = { vPeak1, vPeak2, vPeak3, vPeak4, vPeak5, vPeak6, vPeak7, vPeak8, vPeak9, vPeak10 };
TextBox ^ current = (TextBox ^)sender;
for (int i = 0; i < 10; i++) {
if (textbox[i]->Text != L"") count++;
else count--;
}
this code works for a single textbox, however once I implemented the array of textboxes, the program crashes immediately with an exception at line 4. 'count' has a value of 0.
"'Unable to cast object of type 'System.Windows.Forms.Timer' to type 'System.Windows.Forms.TextBox'.'"
According to the code you posted,
private: System::Void StartBtn_Tick(System::Object^ sender, System::EventArgs^ e)
it would appear this is the Timer's Tick handler. sender is the object that generated the event causing the handler to be called, so in this case
sender would be the Timer itself and not a TextBox. You can't cast sender to a TextBox because it's not a TextBox, it's a Timer.
You can still accomplish the same thing without using sender, though, since you never use it anyway.
array < TextBox ^ > ^ textbox = { vPeak1, vPeak2, vPeak3, vPeak4, vPeak5, vPeak6, vPeak7, vPeak8, vPeak9, vPeak10 };
int count = 0;
for (int i = 0; i < 10; i++) {
if (textbox[i]->Text != L"") count++;
}
// Test the final value of count here and do whatever you need
public partial class Form1 : Form
{
TextBox textBox;
public Form1()
{
InitializeComponent();
textBox = new TextBox() { Height = 30, Width = 200, Text = "Syncfusion Software", Font = new Font("Arial", 11) };
textBox.MouseMove += textBox_MouseMove;
this.Controls.Add(textBox);
}
void textBox_MouseMove(object sender, MouseEventArgs e)
{
var selectionStart = textBox.GetCharIndexFromPosition(e.Location);
textBox.SelectionStart = selectionStart;
textBox.SelectionLength = 0;
}
}
Here is my code, this is simple sample that i tried to get clear information about GetCharIndexFromPosition() method of TextBox.
In Mouse move, i get the char index position using the current mouse position of the textbox and set selection start of the textbox based on it. So that if i mouse move, then the selection start or caret position will be set based on the mouse move. But there was an issue with when mouse moved at the end of text, selection start is not set to last. it sets last but before.
For example, if a text box contains text "stack", then if mouse position is after the "k" then caret position should be at the end, but it displayed inbetween "c" and "k". GetCharIndexPosition() doesn't returns value properly for last character. Let me know solution for this
Thanks in Advance.
Regards,
Venkatesan R
This is a known documented behavior. The Remarks section of the GetCharIndexFromPosition method documentation contains the following Important note:
If the specified location is not within the client rectangle of the control, or is beyond the last character in the control, the return value is the index of the last character.
The workaround is to use the reverse method GetPositionFromCharIndex to adjust the returned index.
Something like this
void textBox_MouseMove(object sender, MouseEventArgs e)
{
var charIndex = textBox.GetCharIndexFromPosition(e.Location);
var charPosition = textBox.GetPositionFromCharIndex(charIndex);
if (e.Location.X > charPosition.X) charIndex++;
textBox.Select(charIndex, 0);
}
P.S. As a side note, I have no idea what this method is trying to achieve, but for sure it prevents the standard text selection by mouse behavior.
This ended up really bothering me, so I expanded Ivan Stoev's idea to a rather overengineered method that calculates the pixel width of the last character and divides it by two to accurately emulate the same behaviour as on the other characters.
The method was written for a drag/drop scenario, where the selection is adjusted to the drop position while hovering the mouse over.
// Cached, so it doesn't get recalculated on each moved pixel.
private Char _textBoxLastChar = '\0';
private Int32 _textBoxLastCharHalfWidth = 0;
private void TextBox_DragOver(object sender, DragEventArgs e)
{
if (!e.Data.GetDataPresent(DataFormats.UnicodeText))
return;
TextBox tb = sender as TextBox;
if (tb == null)
return;
Int32 textLen = tb.Text.Length;
if (textLen > 0 && _textBoxLastChar != tb.Text[textLen - 1])
{
_textBoxLastChar = tb.Text[textLen - 1];
_textBoxLastCharHalfWidth = (Int32)Math.Round(GetStringWidth(_textBoxLastChar.ToString(), tb.Font) / 2);
}
Point localPoint = tb.PointToClient(new Point(e.X, e.Y));
Int32 index = tb.GetCharIndexFromPosition(localPoint);
// fix for fact it returns the last char position when you go outside text bounds.
Int32 charPosition = tb.GetPositionFromCharIndex(index).X;
if (textLen != 0 && index == textLen - 1 && localPoint.X > charPosition + _textBoxLastCharHalfWidth)
index++;
if (!tb.Focused)
tb.Focus();
tb.SelectionStart = index;
tb.SelectionLength = 0;
}
public static Double GetStringWidth(String text, Font f)
{
//create a bmp / graphic to use MeasureString on
Single areaSize = f.Size * 20;
using (Bitmap b = new Bitmap(1, 1))
using (Graphics g = Graphics.FromImage(b))
{
SizeF sizeOfString = g.MeasureString(text, f, new SizeF(areaSize, areaSize), StringFormat.GenericTypographic);
return sizeOfString.Width;
}
}
Of course, if you ever change the font or font size of the text box, you'll have to reset _textBoxLastChar back to '\0'.
I want to declare one button as so: private: System::Windows::Forms::Button^ NewButton; and then make 11 of the same button like this:
int Loc=0;
for(int i=1;i<12;i++)
{
this->NewButton = (gcnew System::Windows::Forms::Button());
this->NewButton->BackColor = System::Drawing::Color::White;
this->NewButton->FlatStyle = System::Windows::Forms::FlatStyle::Flat;
this->TheList->Controls->Add(this->NewButton);
this->NewButton->Location = System::Drawing::Point(-1,Loc-1);
this->NewButton->Size = System::Drawing::Size(200, 30);
this->NewButton->TabIndex = i;
this->NewButton->Text = L"hej"+i;
this->NewButton->Name = L"Button"+i;
this->NewButton->Click += gcnew System::EventHandler(this, &Form2::NewButton_Click);
Loc+=29;
}
private: System::Void NewButton_Click(System::Object^ sender, System::EventArgs^ e) {
NewButton->Text = "Hello";
}
it does now not matter switch button i press to start the event but only the last button made changes it's text. is there a way to have the buttons called by maybe a number or it's name ? if not please give alternative solutions.
NewButton is an instance variable, so on each iteration of your loop you replace it with a different instance - only one can be saved, so that is the last one.
In the button callback when it's pressed you get sender as a parameter which should be the button that was pressed. So, you should use sender to update the text on the button rather than using NewButton.
I am working with the WPF Datagrid and I am trying to enhance/change the copy & paste mechanism.
When the user selects some cells and then hit CTRL + C, the underlying controls is able to catch the CopyingRowClipboardContent event.
this.mainDataGrid.CopyingRowClipboardContent
+= this.DatagridOnCopyingRowClipboardContent;
In this method, some cells are added to both the header and the rows, hence resulting in a "wider" grid.
private void DatagridOnCopyingRowClipboardContent(
object sender,
DataGridRowClipboardEventArgs dataGridRowClipboardEventArgs)
{
// this is fired every time a row is copied
var allContent = dataGridRowClipboardEventArgs.ClipboardRowContent;
allContent.Insert(0, new DataGridClipboardCellContent(
null,
this.mainDataGrid.Columns[0],
"new cell"));
}
At this point I am stuck because I am trying to add an additional row before the header and two after the last row (see image below).
Any ideas? Suggestions?
Please note I am not interested in an MVVM way of doing it here.
Here is a code snippet that might help you.
This snippet is mainly used to retrieve all of your selected data, including headers (I removed the RowHeaders part since you apparently don't need it).
If you have any question please let me know. I left a few part with comments written in capital letters: this is where you should add your own data
The good part of this approach is that it directly works with your DataGrid's ItemsSource and NOT the DataGridCell. The main reason being: if you use DataGridCell on a formatted number for example, you will NOT get the actual value, but just the formatted one (say your source is 14.49 and your StringFormat is N0, you'll just copy 14 if you use a "regular" way)
/// <summary>
/// Handles DataGrid copying with headers
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnCopyingDataGrid(object sender, ExecutedRoutedEventArgs e)
{
// First step: getting the coordinates list of all cells selected
IList<Tuple<int, int>> cellsCoordinatesList = new List<Tuple<int, int>>();
HashSet<int> rowList = new HashSet<int>();
HashSet<int> columnList = new HashSet<int>();
foreach (System.Windows.Controls.DataGridCellInfo cell in this.SelectedCells)
{
int column = cell.Column.DisplayIndex;
int row = this.Items.IndexOf(cell.Item);
cellsCoordinatesList.Add(new Tuple<int, int>(row, column));
if (!rowList.Contains(row))
{
rowList.Add(row);
}
if (!columnList.Contains(column))
{
columnList.Add(column);
}
}
// Second step: Create the table to copy/paste
object[,] arrayToBeCopied = new object[rowList.Count, columnList.Count + 1];
IList<string> colHead = this.ColumnHeaders.Cast<object>().Select(h => h.ToString()).ToList();
for (int row = 0; row < arrayToBeCopied.GetLength(0); row++)
{
for (int column = 0; column < arrayToBeCopied.GetLength(1); column++)
{
if (row == 0)
{
arrayToBeCopied[row, column] = colHead[columnList.ElementAt(column - 1)];
}
else
{
arrayToBeCopied[row, column] = // WHATEVER YOU WANT TO PUT IN THE CLIPBOARD SHOULD BE HERE. THIS SHOULD GET SOME PROPERTY IN YOUR ITEMSSOURCE
}
}
}
// Third step: Converting it into a string
StringBuilder sb = new StringBuilder();
// HERE, ADD YOUR FIRST ROW BEFORE STARTING TO PARSE THE COPIED DATA
for (int row = 0; row < arrayToBeCopied.GetLength(0); row++)
{
for (int column = 0; column < arrayToBeCopied.GetLength(1); column++)
{
sb.Append(arrayToBeCopied[row, column]);
if (column < arrayToBeCopied.GetLength(1) - 1)
{
sb.Append("\t");
}
}
sb.Append("\r\n");
}
// AND HERE, ADD YOUR LAST ROWS BEFORE SETTING THE DATA TO CLIPBOARD
DataObject data = new DataObject();
data.SetData(DataFormats.Text, sb.ToString());
Clipboard.SetDataObject(data);
}
Are you trying to copy the content into e.g. Excel later?
If so, here's what I did:
/// <summary>
/// Copy the data from the data grid to the clipboard
/// </summary>
private void copyDataOfMyDataGridToClipboard(object sender, EventArgs e)
{
// Save selection
int selectedRow = this.myDataGrid.SelectedRows[0].Index;
// Select data which you would like to copy
this.myDataGrid.MultiSelect = true;
this.myDataGrid.SelectAll();
// Prepare data to be copied (that's the interesting part!)
DataObject myGridDataObject = this.myDataGrid.GetClipboardContent();
string firstRow = "FirstRowCommentCell1\t"+ this.someDataInCell2 +"..\r\n";
string lastTwoRows = "\r\nBottomLine1\t" + yourvariables + "\r\nBottomLine2";
string concatenatedData = firstRow + myGridDataObject.GetText() + lastTwoRows;
// Copy into clipboard
Clipboard.SetDataObject(concatenatedData);
// Restore settings
this.myDataGrid.ClearSelection();
this.myDataGrid.MultiSelect = false;
// Restore selection
this.myDataGrid.Rows[selectedRow].Selected = true;
}
In my case I had some static header's which could be easily concatenated with some variables. Important to write are the \t for declaring another cell, \r\n declares the next row
I realize this an older post, but I post this solution for completeness. I could not find a more recent question on copying datagrid rows to clipboard. Using Clipboard.SetData belies the ClipboardRowContent intention.
For my needs, I'm re-pasting back into the e.ClipboardRowContent the row I would like. The cell.Item is all the information I need for each selected row.
Hint: I was getting duplicates without doing an e.ClipboardRowContent.Clear(); after using the e.ClipboardRowContent . I was clearing before and using DataGrid.SelectedItems to build the rows.
private void yourDataGrid_CopyingRowClipboardContent(object sender, DataGridRowClipboardEventArgs e)
{
var dataGridClipboardCellContent = new List<DataGridClipboardCellContent>();
string prevCell = "";
string curCell = "";
foreach (DataGridClipboardCellContent cell in e.ClipboardRowContent)
{
//Gives you access item.Item or item.Content here
//if you are using your struct (data type) you can recast it here curItem = (yourdatatype)item.Item;
curItem = cell.Item.ToString();
if (curCell != prevCell)
dataGridClipboardCellContent.Add(new DataGridClipboardCellContent(item, item.Column, curCell));
prevCell = curCell;
}
e.ClipboardRowContent.Clear();
//Re-paste back into e.ClipboardRowContent, additionally if you have modified/formatted rows to your liking
e.ClipboardRowContent.AddRange(dataGridClipboardCellContent);
}
What is the best way to implement a 2D grid of radio buttons so that only one option in each column and one option in each row can be selected?
A 1D array of 1D arrays of radio buttons. Each row (or column) would use the normal radio button functionality, while each column (or row) would be updated by a loop called whenever an individual radio button is toggled.
Something like this?
using System;
using System.Drawing;
using System.Windows.Forms;
class Program {
static RadioButton[] bs = new RadioButton[9];
static void HandleCheckedChanged (object o, EventArgs a) {
RadioButton b = o as RadioButton;
if (b.Checked) {
Console.WriteLine(Array.IndexOf(bs, b));
}
}
static void Main () {
Form f = new Form();
int x = 0;
int y = 0;
int i = 0;
int n = bs.Length;
while (i < n) {
bs[i] = new RadioButton();
bs[i].Parent = f;
bs[i].Location = new Point(x, y);
bs[i].CheckedChanged += new EventHandler(HandleCheckedChanged);
if ((i % 3) == 2) {
x = 0;
y += bs[i].Height;
} else {
x += bs[i].Width;
}
i++;
}
Application.Run(f);
}
}
Regards,
tamberg
You could approach this with a custom collection and databind the radio buttons.
Each individual child would have a property for row and a property for column as well as the true/false value flag, which raises an event when changed to true via a click or keypress.
Logic in the collection class would respond to the value change and loop through the other children in the same row and column to notify them that they should be false.
If you don't want to databind, you could also do it with a collection of user controls.