Winforms datagrid default values - winforms

This drives me crazy:
i'm using a datagridview (bound to a dataset) and textboxes also bound to the dataset (which means that everything typed into the textboxes is reflected in the datagridview). The datagridview is actually used only to show up the values (user is NOT able to edit anything in the cells).
So: when the user pushes the add new record button (automatically created by visual studio using binding navigator etc. ) i would like to programmatically select this new line (remember: user has not the ability to select with the mouse) and insert some values.
I tried to use the
DefaultValuesNeeded
event, but this is firing only if the user selects the row with the asterisk indicator (what the user is not allowed to do).
How can i simulate that behavior? Should i take another approach?
Thanks in advance!!

Was searching for alternatives to how I currently do this and just wanted to let you know about a couple ways I know of. Both of these will set defaults when using the * (new row) or using a BindingNavigator.
private void CityBindingSource_AddingNew(object sender,
AddingNewEventArgs e)
{
// Simply set the default value to the next viable ID,
// will automatically set the value when needed.
locationDataSet.CITY.CITY_IDColumn.DefaultValue =
CityTableAdapter.GetNewID(); // ++maxID;
}
Or
private void CityDataGridView_DefaultValuesNeeded(object sender,
DataGridViewRowEventArgs e)
{
// Gets called when datagridview's */NewRow is entered.
e.Row.Cells[0].Value = CityTableAdapter.GetNewID(); // ++maxID;
}
private void bindingNavigatorAddNewItem_Click(object sender,
EventArgs e)
{
// BindingNavigator button's AddNewItem automatic tie-
// in to bindingSource's AddNew() is removed.
// This sets the current cell to the NewRow's cell to trigger
// DefaultValuesNeeded event.
CityDataGridView.CurrentCell =
CityDataGridView.Rows[CityDataGridView.NewRowIndex].Cells[1];
}
I was really hoping to use this:
private void CityBindingSource_AddingNew(object sender,
AddingNewEventArgs e)
{
// Cast your strongly-typed
// bindingsource List to a DataView and add a new DataRowView
DataRowView rowView =
 ((DataView)CityBindingSource.List).AddNew();
var newRow = (LocationDTSDataSet.CITYRow)rowView.Row;
newRow.CITY_ID = CityTableAdapter.GetNewID();
newRow.CMMT_TXT = "Whatever defaults I want";
e.NewObject = rowView;
}
But when adding using the BindingNavigator or any code based bindingSource.AddNew() it results in just a blank row. Hopefully someone finds a better way, but this is working for me so far.

Revised:
How about the Datatable's TableNewRow event, then? If that gets fired when the BindingNavigator creates a new row, then that's an opportunity to populate the object.

Related

Winforms ObjectListView: inner OLVColumn instances Name property is empty string so I cannot show/hide columns by name

This question is an offshoot of: Localizing ObjectListView OLVColumn, impossible due to Empty Name property
For simplicity's sake, let's say my ObjectListView contains car information. User A wants to display only Make and Model columns. User B only wants to display Model and Year columns. These preferences would be saved to/loaded from an .ini file on the users' local machines.
I cannot loop through the columns of the ObjectListView and do if (col.Name == colNameFromIni) { col.Visible == true; } because the .Name property of every column is an empty string ("") and does not get serialized to the designer codebehind file. This never happens with any other Winforms control (Label, Button, etc.) They always get their .Name written to the designer codebehind.
In some sense, this is a flaw in Winforms itself, because OLVColumn inherits from System.Windows.Forms.ColumnHeader, and a traditional ListView has exactly the same problem. .Name is always an empty string for all columns.
I would like to patch our local build of ObjectListView.dll to force populate the .Name property, but I can't figure out how Winforms automagically knows the name of every control on the form. It somehow(?) knows the names of the OLVColumn objects since it can display them in the Edit Columns... dialog on the ObjectListView's context menu. I'm also a little fuzzy on where the best spot is to plug this in.
(Yes, per linked question at top I know that as a last resort, I can hardcode colXX.Name = "colXX"; for all columns in my source code, but future column additions are likely to get overlooked and a programmatic solution is much preferred.)
(See also: https://sourceforge.net/p/objectlistview/bugs/160/ : the ObjectListView author declared this a wont-fix so it is up to me (or us), I guess.)
As you point out, this is a bug which is not with the ObjectListView, but the underlying component. And a bug which is around since at least 2008! Therefore, I doubt it will ever be fixed by MS.
Actually, it is a problem with the Autogenerated code in the designer.
If you look at other components such as a button, then the autogenerated code adds a name such as this;
//
// button2
//
this.button2.Location = new System.Drawing.Point(458, 199);
this.button2.Name = "button2";
...
But for ColumnHeader (Listview) and OLVColumn (ObjectListView), then this is not done, so then you end up with this.
//
// olvColumn1
//
this.olvColumn1.AspectName = "Name";
this.olvColumn1.Text = "Name";
If you manually add the line
this.olvColumn1.Text = "olvColumn1";
Then the "problem" is solved.
Of course, you can't do this, because the designer will override the autogenerated code when you make any changes, and then you will lose these manually added lines. It is also not sustainable.
So I'm afraid you need to code around this with some kind of ugly solution. Some options are:
Use the Tag to store the name and compare against this.
Use the text instead of the name (not possible if you have multi
language support!)
Code the names column manually in the Constructor
Set the Text to be something like "ColName;ColText" and then in your
code separate these out.
I have done option 3 in the past, but only I was maintaining the code, so this was easy.
What you could do to ensure you don't have discrepancies is to add a check in your constructor to compare the actual number of columns with the number you expect (hard coded for), and throw an exception if they don't match. Also, not the best, but another way to highlight and reduce errors.
The workaround for this is to get the OLVColumns via reflection and set their column's Name property at runtime. Every OLVColumn is a form-level field, so just pick them out of the list returned by GetFields().
Dim allFieldInfos As FieldInfo() = GetType(FrmMain).GetFields(BindingFlags.NonPublic or BindingFlags.Instance)
For Each fi As FieldInfo In allFieldInfos
If fi.FieldType Is GetType(OLVColumn) Then
Dim instance As OLVColumn = fi.GetValue(Me)
For Each col As OLVColumn In fdlvMain.AllColumns
If ReferenceEquals(col, instance) Then
col.Name = fi.Name
End If
Next
End If
Next

Extjs 6.0 - ItemSelector: how to programmatically focus/highlight an element?

I have a ItemSelector component inside a Window. I have implemented a search functionality that dynamically finds the matching entry based on user's keyboard input.
Now I just want to highlight/focus such item inside the ItemSelector.
I'm looking for something like:
// when the search returned a result and got the related index in the store
function onSearchPerformed(index) {
var cmp = this;
cmp.itemSelector.setSelected(index); // here I'd be highlighting the entry
}
Example
Imagine a simple ItemSelector like this one taken from the web.
User types 'Delaw' and my search function detects that there is an entry with name Delaware and it's at position 3 in the store.
All I want to do is to programmatically highlight the row/entry 'Delaware' just as if you clicked on it.
This ux component uses a boundList, or better 2 of them.
A from and a toList.
You need to get a reference to the right boundlist.
More on that you will find here: http://docs.sencha.com/extjs/6.0.1/classic/src/ItemSelector.js.html
Basically you can do something like this:
https://fiddle.sencha.com/#view/editor&fiddle/24ec
afterrender: function(cmp){
Ext.defer(function(){
var boundlist = cmp.down('boundlist');
item = boundlist.all.item(1);
boundlist.highlightItem(item);
},300);
}
After you have a ref to the correct boundlist, you can simply highlight the item using:
http://docs.sencha.com/extjs/6.0.1/classic/Ext.view.BoundList.html#method-highlightItem
Take care that you may need to call following function before:
http://docs.sencha.com/extjs/6.0.1/classic/Ext.view.BoundList.html#method-clearHighlight
To find the correct item should't be too hard.
There are two ways to solve the issue
One is by following #devbnz answer, marked as correct. This is preferable, if you just want to graphically highlight the entry without triggering any event.
However, by hovering on other entries, you will lose the highlight on your current entry.
The second one, as suggested by #guilherme-lopes in the comments, may be preferable in cases in which you want the selection to act as if you actually clicked on an entry, which will trigger the selectionchange event and similar...
Depending on the situation, I ended up using either.

How to paste tabular data into the "new row" of a DataGridView with AllowUserToAddRows set true?

We use DataGridViews throughout our UI... most are data-bound to some backing table... most of those through a BindingSource.
They all have supported Ctrl+C to Copy tabular data out (so you can paste into Excel or Word tables, etc.). That was easy... it's built-in... just set MultiSelect true and ClipboardCopyMode to EnableWithAutoHeaderText (or one of the other Enable... settings).
Our users have requested us to similarly support Ctrl+V to Paste tabular data copied from Excel etc. Unfortunately, that is not nearly so easy. I created a generic utility method PasteStringTable(DataGridView) that will pull the tabular data off the Clipboard and then paste it into the selected cells of the argument DataGridView. I have that working except for one key issue:
If the DataGridView has AllowUserToAddRows set true, and the user is in that magic "new row" at the bottom, then I wanted to add rows for each row on the Clipboard. But the problem is, nothing I do programmatically ever seems to get it to actually add that row... I've tried everything that I can think of (or find via Bing or Google):
(1) I tried doing BeginEdit / EndEdit on the DataGridView.
(2) I tried first setting the DataGridView.CurrentCell to the cell I was about to edit, then DataGridView.BeginEdit(true), then edit the cell by setting its Value property, then DataGridView.EndEdit().
(3) I tried doing #2, but where I did SendKeys.Send(value) instead of .Value = value.
(4) I tried doing DataGridView.NotifyCurrentCellDirty(true) in the middle of the above.
(5) I tried modifying the underlying data source first by manually doing DataGridView.Rows.Insert(index, 1) to try to force in a new row before I pasted into it... but that throws an exception since my DataGridViews are data-bound. (NOTE: I do not want to hard-code specific access to the underlying data because I want it to work like Paste where the data copied lines up visually with the DataGridView's columns.)
(6) Many other variations...
In the end, most attempts result in the same behavior: you can see that the bottom row gets modified... you can see the first row of the data you copied show up in that magic "new row", until you click out of the row and the row goes blank again... unless you manually edit it, it never gets added to the underlying data. Even if I try doing SendKeys.SendWait(...), it still doesn't act like it would if I typed in that data.
How do I programmatically get that magic "new row" to act like it would if I typed in the pasted in data??
(Surely I am not the first person to want to paste tabular data into DataGridViews.)
After much trial and error, this is the pattern I came up with that works well in most cases. This is pure guesswork, though, so if anybody knows how it is supposed to work, please let us know. Anyway, here's what I've got working:
DataGridViewCell cell = dgv[colIndex, rowIndex];
if (!cell.ReadOnly) // && (evenIfNotSelected || cell.Selected)) // Selected is not stable in some cases
{
if (!cell.OwningRow.IsNewRow)
cell.Value = cellData;
else
{
dgv.CurrentCell = cell;
dgv.BeginEdit(true);
dgv.NotifyCurrentCellDirty(true);
//SendKeys.SendWait(cellData);
cell.Value = cellData;
//dgv.CommitEdit(???);
dgv.EndEdit();
//PSMUtility.DoMessages();
// Do it again to try to overwrite the default value that might get injected by EndEdit adding the row
cell.Value = cellData;
}
}
Also i try many solution and finally it's work in my case.
private void calculationGrid_KeyPress(object sender, KeyPressEventArgs e)
{
if ((int)e.KeyChar == 22 && Control.ModifierKeys == Keys.Control)
{
calculationGrid.CurrentCell.Value = Clipboard.GetText().Replace("\r", "").Replace("\n", "").Trim();
calculationGrid.BeginEdit(true);
calculationGrid.NotifyCurrentCellDirty(true);
calculationGrid.EndEdit();
}
}

Assign instance id when the wpf app has multiple controls on page identified as one instance

Is it possible to use searchProperties.Add("Instance","1"); to assign instance id where the wpf application has multiple controls like buttons or combo boxes which are recognized as one instance of the control. If yes then how to do this? Any code sample is appreciated.
An addendum to AdrianHHH's excellent suggestion is to iterate through the FindMatchingControls collection and figure out which one is the one you want. If it's always collection[3] then you can identify it and act on it as such.
Your code would look like this:
var button = new WinButton(winWindowChild(mainParentCtName, childParentCtName));
//if you pass parents when you construct an object it can speed up find
button.SearchProperties.Add(/*any identifying properties*/);
var allButtons = button.FindMatchingControls();
//test code
foreach (var btn in allButtons)
Mouse.Click(btn); //watch this to figure out what order the buttons are iterating through, then remove loop
Mouse.Click(allButtons[3]);
Mouse.DoubleClick(allButtons[0]);
Mouse.Hover(allButtons[1]);

ADO.NET databinding bug - BindingSource.EndEdit() changes current position

What is the correct order of processing an insert from a data-bound control using BindingSource, DataSet, and TableAdapter? This is causing me eternal confusion.
I have a form that is used to add a new row.
Before showing the form, I call:
bindingSource.AddNew();
bindingSource.MoveLast();
Upon Save, I call:
bindingSource.EndEdit();
tableAdapter.Insert([the row given to me as bindingSource.Current]);
The problem is that
if I don't call EndEdit(), the changes of the TextBox with the current focus are not saved
if I do call EndEdit(), the BindingSource's Current member no longer points to the row that I just added.
I can of course call Insert() with the values from the form as opposed to the DataTable that was updated by the BindingSource, but that defeats the purpose of using data binding. What do I need to do in order to get this working?
I understand that I could call TableAdapter.Update() on the entire DataSet, since I am using a strongly typed DataSet. I have foreign keys in the table that are not datab-bound, though, and that I am adding in before I call Insert().
It turns out that this is a 'feature' of the .NET framework. I has been previously reported on connect.microsoft.com, but the issue was closed as "will not fix". There is a workaround, though.
I am using the following C# code to reset the position in a ListChanged event handler:
[...]
bindingSource.ListChanged +=
new ListChangedEventHandler(PreserveCurrentPosition);
[...]
private void PreserveCurrentPosition(object sender, ListChangedEventArgs e)
{
if (e.ListChangedType == System.ComponentModel.ListChangedType.ItemAdded &&
((BindingSource)sender).Count - e.NewIndex > 1)
{
((BindingSource)sender).Position = e.NewIndex;
}
}
You have probably figured this out by now, but you don't need to call the insert method of the table adapter. Just call the update method, and it will determine if there are any new or changed records and act accordingly.

Resources