C# objectlistview undefined offset in cell editor - c#

I use TreeListView component from ObjectListView library. I make cell values editable and when i double click on them TextBox will appear with odd offset.
I want to remove this offset, but how?
before start editing
after start editing
As you can see first row, second column ("My new device"), TextBox is appeared with offset.
P.S. Editing work as expected. Only offset is annoying me
P.P.S. As you can see the offset depends of first column offset. How i can change it to zero?

After long searches i found solution!
Source code of OLV, ObjectListView.cs
public virtual void StartCellEdit(OLVListItem item, int subItemIndex) {
OLVColumn column = this.GetColumn(subItemIndex);
Control c = this.GetCellEditor(item, subItemIndex);
Rectangle cellBounds = this.CalculateCellBounds(item, subItemIndex);
c.Bounds = this.CalculateCellEditorBounds(item, subItemIndex, c.PreferredSize);
// Try to align the control as the column is aligned. Not all controls support this property
Munger.PutProperty(c, "TextAlign", column.TextAlign);
// Give the control the value from the model
this.SetControlValue(c, column.GetValue(item.RowObject), column.GetStringValue(item.RowObject));
// Give the outside world the chance to munge with the process
this.CellEditEventArgs = new CellEditEventArgs(column, c, cellBounds, item, subItemIndex);
this.OnCellEditStarting(this.CellEditEventArgs);
if (this.CellEditEventArgs.Cancel)
return;
// The event handler may have completely changed the control, so we need to remember it
this.cellEditor = this.CellEditEventArgs.Control;
this.Invalidate();
this.Controls.Add(this.cellEditor);
this.ConfigureControl();
this.PauseAnimations(true);
}
I saw CellEditEventArgs contains Control, that drawed in area named Bounds.
This function in the source file append offset to control's bounds:
protected Rectangle CalculateCellEditorBoundsStandard(OLVListItem item, int subItemIndex, Rectangle cellBounds, Size preferredSize) {
if (this.View == View.Tile)
return cellBounds;
// Center the editor vertically
if (cellBounds.Height != preferredSize.Height)
cellBounds.Y += (cellBounds.Height - preferredSize.Height) / 2;
// Only Details view needs more processing
if (this.View != View.Details)
return cellBounds;
// Allow for image (if there is one).
int offset = 0;
object imageSelector = null;
if (subItemIndex == 0)
imageSelector = item.ImageSelector;
else {
// We only check for subitem images if we are owner drawn or showing subitem images
if (this.OwnerDraw || this.ShowImagesOnSubItems)
imageSelector = item.GetSubItem(subItemIndex).ImageSelector;
}
if (this.GetActualImageIndex(imageSelector) != -1) {
offset += this.SmallImageSize.Width + 2;
}
// Allow for checkbox
if (this.CheckBoxes && this.StateImageList != null && subItemIndex == 0) {
offset += this.StateImageList.ImageSize.Width + 2;
}
// Allow for indent (first column only)
if (subItemIndex == 0 && item.IndentCount > 0) {
offset += (this.SmallImageSize.Width * item.IndentCount);
}
// Do the adjustment
if (offset > 0) {
cellBounds.X += offset;
cellBounds.Width -= offset;
}
return cellBounds;
}
We can see, that offset appending to every cell (not only first). Also we can se, that CellEditEventArgs contains CellBounds.
So we can just reset Control.Bounds to CellBounds value:
//...
dataTreeView.CellEditStarting += DisableInputValueForCollections;
//...
private static void DisableInputValueForCollections(object sender, CellEditEventArgs e)
{
RemoveExtraOffsetForNotFirstColumnInputControl(e);
var node = e.RowObject as DataTreeNode;
if (node != null && e.Column.AspectName == "Value")
{
if (node.IsContainer()) e.Cancel = true;
}
}
//...
private static void RemoveExtraOffsetForNotFirstColumnInputControl(CellEditEventArgs e)
{
if (e.Column.AspectName != "Name")
{
e.Control.Bounds = e.CellBounds;
}
}
//...

The issue still exists in OLV v2.9.1. I worked around it slightly different than muzagursiy did.
olv.CellEditStarting += (sender, args) =>
{
// Left align the edit control
args.Control.Location = args.CellBounds.Location;
// Readjust the size of the control to fill the whole cell if CellEditUseWholeCellEffective is enabled
if (args.Column.CellEditUseWholeCellEffective)
{
args.Control.Size = args.CellBounds.Size;
}
};

Related

Image and value into a GridView cell

I have a XtraGridview bound to datasource via Linq, when i check some checkbox i need to set an image into a cell and the value they already have.
right now when i check the checkbox i set the image in the cell just fine but eliminate the cell value (the data).
On CustomDrawCell event i do this
private void gridView_GD_CustomDrawCell(object sender, RowCellCustomDrawEventArgs e)
{
GridView view = sender as GridView;
string evento1 = Convert.ToString(view.GetRowCellValue(e.RowHandle, "Eve1"));
if (CVariables.Ficon_estado == 1)
{
if (evento1 == "06" || evento1 == "15")
{
if (e.Column.FieldName == "G1")
{
e.Handled = true;
Point pos = CalcPosition(e, imageCollection_16.Images[1]);
e.Graphics.DrawImage(imageCollection_16.Images[1], pos);
view.Columns["G1"].AppearanceCell.BackColor = Color.Transparent;
view.Columns["G1"].AppearanceCell.TextOptions.HAlignment = DevExpress.Utils.HorzAlignment.Far;
}
}
}
else
{
view.Columns["G1"].AppearanceCell.TextOptions.HAlignment = DevExpress.Utils.HorzAlignment.Center;
}
}
private Point CalcPosition(RowCellCustomDrawEventArgs e, Image img)
{
Point p = new Point();
p.X = e.Bounds.Location.X + (e.Bounds.Width - (img.Width * 3)) / 2;
p.Y = e.Bounds.Location.Y + (e.Bounds.Height - img.Height) / 2;
return p;
}
I post an image to illustrate what i want
A cell text disappears since you set the e.Handled property to true. When this option is set, a default painting mechanism is not invoked. You can still draw a cell text manually by using the e.Appearance.DrawString method.
e.Appearance.DrawString(e.Cache, e.DisplayText, e.Bounds);
Another solution is to show an image by using the RepositoryItemTextEdit.ContextImage. That is, you can create two RepositoryItemTextEdits with different context images and assign them to grid cells conditionally in the GridView.CustomRowCellEdit event handler.

Edit ListView Group Headers like we can edit ListViewItems

In a WinForms app, we can re-name ListView Items by clicking them twice. Can we somehow rename Group Headers the same way? Is there a way to enable this?
I guess it is doable after all, albeit not by simply enabling a property..
Note: The code below assumes that the ListView is in Details mode!
The trick to tell a Group from emtpy space is to test the right side of the ListView. Another trick is to wait a little: The click will select the Group Items. Only after that can we proceed..
Here is an example that overlays the Group with a TextBox:
// class variable to test if have been hit twice in a row
ListViewGroup lastHitGroup = null;
private void listView1_MouseDown(object sender, MouseEventArgs e)
{
// check left side to see if we are at the empty space
ListViewItem lvi = listView1.GetItemAt(4, e.Y);
// yes, no action! reset group
if (lvi != null) { lastHitGroup = null; return; }
// get the height of an Item
int ih = listView1.GetItemRect(0).Height;
// to get the group we need to check the next item:
ListViewItem lviNext = listView1.GetItemAt(4, e.Y + ih);
// no next item, maybe the group is emtpy, no action
if (lviNext == null) return;
// this is our group
ListViewGroup editedGroup = lviNext.Group;
// is this the 2nd time?
if (lastHitGroup != editedGroup) {lastHitGroup = editedGroup; return;}
// we overlay a TextBox
TextBox tb = new TextBox();
tb.Parent = listView1;
// set width as you like!
tb.Height = ih;
// we position it over the group header and show it
tb.Location = new Point(0, lviNext.Position.Y - ih - 4);
tb.Show();
// we need two events to quit editing
tb.KeyPress += (ss, ee) =>
{
if (ee.KeyChar == (char)13) // success
{
if (editedGroup != null && tb.Text.Length > 0)
editedGroup.Header = tb.Text;
tb.Hide();
ee.Handled = true;
}
else if (ee.KeyChar == (char)27) // abort
{
tb.Text = ""; tb.Hide(); ee.Handled = true;
}
};
tb.LostFocus += (ss, ee) => // more success
{
if (editedGroup != null && tb.Text.Length > 0)
editedGroup.Header = tb.Text;
tb.Hide();
};
// we need to wait a little until the group items have been selected
Timer lvTimer = new Timer();
lvTimer.Interval = 333; // could take longer for a huge number of items!
lvTimer.Tick += (ss,ee) => { tb.Focus(); lvTimer.Stop();};
lvTimer.Start();
}

problems updating picture box control

I have two controls on the same form. Both controls contain an ObjectListView control. The one listview was created with the graphical editor in visual studio. This one is not causing any issues. The other listview is created programmatically at run-time. I have defined an event handler for each control that gets called when the hot item changes and they are both firing when they should. Both event handlers call the same code to update a picturebox control. The problem is that the picturebox does not get updated when the programmatically defined listview is asking it to. I am positive the event handler is getting called because my code writes to a text file as well as updating the picture box. The text file gets updated but the picture box does not. I have tried updating, invalidating, and refreshing the PicutureBox as well as the parent form, but I just can not get it to update.
I am not sure if this is an ObjectListView issue or a standard WinForms problem. I realize my question is very vague but I am not sure how to clarify it without posting all my code. Any advice would be appreciated.
Here is the code that the event handler calls:
public void ShowBitmap(object sender, HotItemChangedEventArgs e, ObjectListView lv, string type)
{
ObjectListView olv = sender as ObjectListView;
if (sender == null)
{
return;
}
switch (e.HotCellHitLocation)
{
case HitTestLocation.Nothing:
break;
case HitTestLocation.Group:
break;
case HitTestLocation.GroupExpander:
break;
default:
if (e.HotColumnIndex == 0)
{
pictureBox1.Hide();
pictureBox1.BorderStyle = BorderStyle.FixedSingle;
int rowIndex = e.HotRowIndex;
string text = "";
if (type == "Main Parts")
{
TypedObjectListView<MainRadanProjectPartsPart> tlist = new TypedObjectListView<MainRadanProjectPartsPart>(lv);
text = tlist.Objects[rowIndex].Symbol;
}
else if (type == "Parts")
{
TypedObjectListView<RadanProjectPartsPart> tlist = new TypedObjectListView<RadanProjectPartsPart>(lv);
text = tlist.Objects[rowIndex].Symbol;
}
else if (type == "Nests")
{
TypedObjectListView<MainRadanProjectNestsNest> tlist = new TypedObjectListView<MainRadanProjectNestsNest>(lv);
text = tlist.Objects[rowIndex].FileName;
}
if (text != null)
{
Point screenCoords = Cursor.Position;
Point controlRelatedCoords = lv.PointToClient(screenCoords);
if (controlRelatedCoords.Y < oldCursorPosition.Y)
{
pictureBox1.Location = controlRelatedCoords;
int xPos = controlRelatedCoords.X;
int yPos = controlRelatedCoords.Y + 60;
pictureBox1.Location = new Point(xPos, yPos);
}
else if (controlRelatedCoords.Y > oldCursorPosition.Y)
{
pictureBox1.Location = controlRelatedCoords;
int xPos = controlRelatedCoords.X;
//int yPos = controlRelatedCoords.Y - pictureBox1.Height;
int yPos = controlRelatedCoords.Y - pictureBox1.Height + 30;
pictureBox1.Location = new Point(xPos, yPos);
}
pictureBox1.Show();
pictureBox1.BringToFront();
olvTreeViewMainParts.Focus();
lv.Focus();
pictureBox1.Visible = true;
DrawSymbol(text);
oldCursorPosition = controlRelatedCoords; // save the cursor position to track cursor direction between calls
}
else
{
DrawSymbol("");
}
}
else
{
pictureBox1.Hide();
}
break;
}
}
Here is the event handler for the programmaticaly defined listview:
// track the cursor as it moves over the items in the listview
private void olvPartsListView_HotItemChanged(object sender, HotItemChangedEventArgs e)
{
ShowBitmap(sender, e, olvPartsListView, "Parts");
}

Drag one picturebox to picturebox, it drags them all

When i drag one picturebox, it drags both of mine. Because in the pbxMap_DragDrop method i have to call both of the methods that should fire when i drag one.
private void pbxMap_DragDrop(object sender, DragEventArgs e)
{
myDetectMouse.setMinotaur(e, myMap.myCells);
myDetectMouse.setTheseus(e, myMap.myCells);
}
SetTheseus:
public void setTheseus(DragEventArgs e, List<Cell> cells)
{
for (int i = 0; i < cells.Count; i++)
{
int[] mapData = myMapController.getMapData(i, cells);
int column = mapData[0];
int row = mapData[1];
int right = mapData[2];
int bottom = mapData[3];
Point RelativeMouseLoc = myMapController.myMap.myForm.pbxMap.PointToClient(Cursor.Position);
if (RelativeMouseLoc.X > column &&
RelativeMouseLoc.X < column + myMapController.myMap.myCellSize
&& RelativeMouseLoc.Y > row && RelativeMouseLoc.Y <
row + myMapController.myMap.myCellSize)
{
myMapController.myMap.myCells[i].hasTheseus = true;
}
else
{
myMapController.myMap.myCells[i].hasTheseus = false;
}
}
}
SetMinotaur is much the same but replace hasTheseus with hasMinotaur. As soon as a cell "hasTheseus" or "hasMinotaur" it will be drawn to the cell.
So it draws them both when i drag one because they both get set in pbxMap_DragDrop.
I thought i could have multiple event handlers for pbxMap_DragDrop depending on which picturebox was dragged.
You can check the sender parameter to determine whether you want to move the minotaur or Theseus. It would look something like this:
var pic = (PictureBox)sender;
if (pic.Name == "minotaur")
{
myDetectMouse.setMinotaur(e, myMap.myCells);
}
else
{
myDetectMouse.setTheseus(e, myMap.myCells);
}
If you don't want to use the Name property, you can use something else like the Tag property - just make sure you set it for each of the PictureBox objects.

How can I get navigation (tab/arrow) through cells on a DataGridView to skip over the read-only cells?

I have a DataGridView in C# (.NET 2.0) with a few read-only cells (3 out of 25). I'm using the method outlined in the top answer here to set ReadOnly on those specific cells in the Form_Load handler. The linked question states that
some cells has to be ReadOnly and also when the user navigates with TAB or ENTER between cells, the ReadOnly cells should be bypassed
so I assumed setting the flag would cause the cells to be skipped.
Long story short, it's not. The read-only cells get tabbed to and selected even though they cannot be edited. I'm combing over the properties of the DataGridView looking for some kind of TabMode or TabSkipsReadOnlyCells property, but so far nothing. Is there a property to set for this behavior, or do I have to write some tab event handling code of some kind?
This seems like it should be the default behavior, so I'm kind of annoyed to even have to find a property for it, much less have to write code to do it...
EDIT: I should clarify, I'm not interested in only handling navigation with the Tab key. I want to implement reasonable navigation with the arrow keys and possibly the mouse as well. That means if I have to write code I need direct control over where the selection moves when I bounce it out of a read-only cell, probably by setting CurrentCell on the DataGridView. This is so that if the user up-arrows into a read-only cell I can redirect to the cell above, not always to the cell to the right.
EDIT 2: Here's my final solution, handling arrow key navigation as well as tab navigation, based on Sean Griffiths' code (also linked in the accepted answer):
private void GridForm_Load(object sender, EventArgs e)
{
dataGridView1.CellEnter += dataGridView1_CellEnter;
}
//a delegate is needed to avoid a circular loop when selecting a cell when in a cell selection event
private delegate void SetColumnAndRowOnGrid(DataGridView grid, int columnIndex, int rowIndex);
static SetColumnAndRowOnGrid setCellMethod = new SetColumnAndRowOnGrid(setGridCell);
// Method pointed to by the delegate
private static void setGridCell(DataGridView grid, int columnIndex, int rowIndex)
{
grid.CurrentCell = grid.Rows[rowIndex].Cells[columnIndex];
grid.BeginEdit(true);
}
// Track the cell we leave so we can determine direction of "travel"
int _lastRow = 0, _lastCol = 0;
private void dataGridView1_CellLeave(object sender, DataGridViewCellEventArgs e)
{
_lastRow = e.RowIndex;
_lastCol = e.ColumnIndex;
}
enum Direction { Up, Down, Left, Right }
// When we enter a read only cell, determine direction
// of "travel" and keep going that way
private void dataGridView1_CellEnter(object sender, DataGridViewCellEventArgs e)
{
int currRow = e.RowIndex;
int currCol = e.ColumnIndex;
if (dataGridView1.Rows[currRow].Cells[currCol].ReadOnly)
{
Direction direction = Direction.Right;
if ((currRow != _lastRow) && (currCol == _lastCol))
{
// moving vertically
if (currRow < _lastRow) direction = Direction.Up;
else direction = Direction.Down;
}
else
{
// moving horizontally
if (currCol == 0 &&
_lastCol == dataGridView1.Columns.Count - 1 &&
currRow == _lastRow + 1)
{
// Special case - probably just tabbed from end of row
direction = Direction.Right;
}
else if (currCol == dataGridView1.Columns.Count - 1 &&
_lastCol == 0 &&
currRow == _lastRow - 1)
{
// Special case - probably just shift-tabbed from start of row
direction = Direction.Left;
}
else if (currCol < _lastCol) { direction = Direction.Left; }
}
//this cell is readonly, find the next tabable cell
if (!SetNextTabableCell(dataGridView1, currCol, currRow, direction))
{
// All the cells in the grid have been tried, none could be tabbed
// to so move onto the next control
bool tabForward = direction == Direction.Right || direction == Direction.Down;
SelectNextControl(this, tabForward, true, true, true);
}
}
}
// Find the next cell that we want to be selectable
private static bool SetNextTabableCell(DataGridView grid, int nextColumn, int nextRow, Direction direction)
{
//keep selecting each next cell until one is found that isn't either readonly or invisible
int maxMoves = grid.ColumnCount * grid.RowCount;
int moves = 0;
do
{
if (!GetNextCell(grid, ref nextColumn, ref nextRow, ref direction)) return false;
// Prevent infinite loop - I managed to get in one when this function
// wound up in a readonly column with a direction of Down (if we've moved
// to another cell more times than there are cells in the grid, just give up)
if (++moves > maxMoves) return false;
}
while (grid.Rows[nextRow].Cells[nextColumn].ReadOnly == true ||
grid.Rows[nextRow].Cells[nextColumn].Visible == false);
//a cell has been found that can be entered, use the delegate to select it
grid.BeginInvoke(setCellMethod, grid, nextColumn, nextRow);
return true;
}
// Get the next cell in the indicated direction
// Wrap around if going left-right
// Bounce at the edge if going up/down
private static bool GetNextCell(DataGridView grid, ref int nextColumn, ref int nextRow, ref Direction direction)
{
switch (direction)
{
case Direction.Right:
if (nextColumn < grid.Columns.Count - 1)
{
// Nominal case - move right one cell
nextColumn = nextColumn + 1;
}
else // at the last column
{
// go the the first column
nextColumn = 0;
if (nextRow < grid.Rows.Count - 1)
{
// Nominal case - move down one row
nextRow = nextRow + 1;
}
// at the last row and last column exit this method, no cell can be selected
else { return false; }
}
break;
case Direction.Left:
if (nextColumn > 0)
{
// Nominal case - move left one cell
nextColumn = nextColumn - 1;
}
else // at the first column
{
// go the the last column
nextColumn = grid.Columns.Count - 1;
if (nextRow > 0)
{
// Nominal case - move up one row
nextRow = nextRow - 1;
}
// at the first row and first column exit this method, no cell can be selected
else { return false; }
}
break;
case Direction.Down:
if (nextRow < grid.Rows.Count - 1)
{
// Nominal case - move down one cell
nextRow = nextRow + 1;
}
else // at the last row
{
// turn around
nextRow = nextRow - 1;
direction = Direction.Up;
}
break;
case Direction.Up:
if (nextRow > 0)
{
// Nominal case - move up one cell
nextRow = nextRow - 1;
}
else // at the first row
{
// turn around
nextRow = nextRow + 1;
direction = Direction.Down;
}
break;
default: return false;
}
return true;
}
If anyone uses this and finds cases where it behaves badly, I'd like to hear about it so I can hopefully update this with a fix.
EDIT 3: Added a safety counter after the code managed to get itself in an infinite-loop state today. All cells in column zero were set to read-only, and the first click into the grid control was in column zero, so it tried to move down, then up, then down....
You will have put in some code for that
One way of doing it is
void grd_CellEnter(object sender, DataGridViewCellEventArgs e)
{
if(grd[e.ColumnIndex,e.RowIndex].ReadOnly)
SendKeys.Send("{TAB}");
}
I had a similar problem using a datagridview - there is a write up on my solution here http://codemumbler.blogspot.com/2011/02/one-aspect-of-datagridviews-that-you.html
It uses the CellEnter eventhandler and hunts for the next available usable sell in the grid and uses a delegate to avoid a Reentrant exception.
Hope this helps - Sean

Categories