Complex Grid in Winforms - c#

I need to display data in WinForms in a very complex way.
There are many rows, and each row needs to be displayed slightly different.
Each row's model has a list of entities inside it, each translated to a button.
The buttons for each row are presnted in specific way according to some rules. Each row has different amount of buttons displayed in a different way in rows and cols.
On top of that, the display needs to be binded to the model and listen for many frequent changes, not only for value changes to display but also calculated data that indicates things like if to display the row or not, or to display the button or not, color indicators and more.
Now it is implemented with Telerik GridView, and, as you might expect, it doesn't function very well, too much data. The scroll bar is faltering, on many occations the rows or cells are not displayed until the user scrolls a bit.
This is an example of how I would like it to be displayed (without colors and stuff)
This is a very short sample of the code:
public class CustomCellElement : GridDataCellElement
{
protected override SizeF ArrangeOverride(SizeF finalSize)
{
try
{
float x = finalSize.Width;
float y = 0;
float maxHeight = 0;
foreach (var child in this.Children.ToList())
{
try
{
var element = child as RadButtonElement;
arrange logic...
child.Arrange(location);
.
.
------------
protected override void CreateChildElements()
{
_childItems = new ThreadSafedBindingList<E>();
_childItemsBindings = new List<Binding>();
base.CreateChildElements();
}
protected override void SetContentCore(object value)
{
...
foreach (var someChild in childs.ToList())
{
AddItem(someChild);
}
...
....
protected override void SetBindings(SomeChildEntity childElement, CustomRadButtonElement item)
{
....
Creating many bindings necessary for each SomeChildEntity
....
}
....
I will be very glad to hear suggestions !
Thanks

Related

CollectionView cell height is not automatically resizing

So I am encountering some issues with a UICollectionView I have in my Xamarin iOS project.
The cells for the CollectionView are not expanding with the label in them. The label in the cell has lines set to 0 and the following constraints:
(top, bottom, leading, trailing) to superview
Height >= 10
In my ViewDidLoad() I setup the CollectionView as follows:
var source = new CustomCollectionViewSource(collectionView);
collectionView.Source = source;
collectionView.SetCollectionViewLayout(new UICollectionViewFlowLayout()
{
ScrollDirection = UICollectionViewScrollDirection.Vertical,
EstimatedItemSize = new CGSize(collectionView.Frame.Width, 10)
}, false);
var set = this.CreateBindingSet<MainViewController, MainViewModel>();
set.Bind(source).For(v => v.ItemsSource).To(vm => vm.DataSource);
set.Apply();
and my CollectionViewSource
public class CustomCollectionViewSource : MvxCollectionViewSource
{
public IList<Item> Messages { get; set; }
public override void ReloadData()
{
var list = (ItemsSource as IEnumerable<Item>).ToArray();
Messages = list;
}
public CustomCollectionViewSource(UICollectionView collectionView) : base(collectionView)
{
collectionView.RegisterNibForCell(CustomCollectionViewCell.Nib, CustomCollectionViewCell.Key);
}
public override UICollectionViewCell GetCell(UICollectionView collectionView, NSIndexPath indexPath)
{
var cell = collectionView.DequeueReusableCell(CustomCollectionViewCell.Key, indexPath) as CustomCollectionViewCell;
cell.DataContext = Messages.ElementAtOrDefault(indexPath.Row);
return cell;
}
}
I am trying to have the cells be the width of the collection view and for the height to be flexible; however, whenever I populate the cells, I always get something like this:
The cells are cutoff and have an ellipses at the end. I've read online that the constraints must be right to dynamically resize, so is there something I'm missing?
I also read that to get a calculated height for each cell I can load the nib of the cell, populate it, then set the size manually in a custom UICollectionViewDelegate. Cool idea, but I could find any working examples for Xamarin iOS.
Please let me know if there is something I'm missing or any other info you require. Thanks in advance!
1.Make your label support multiple lines by
textLabel.LineBreakMode = UILineBreakMode.WordWrap;
textLabel.Lines = 0;
2.Set the cell estimated height
cell.estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize
Things to note:
To define the cell’s height, you need an unbroken chain of
constraints and views (with defined heights) to fill the area
between the content view’s top edge and its bottom edge.
If your views have intrinsic content heights, the system uses those
values. If not, you must add the appropriate height constraints,
either to the views or to the content view itself.

What kind of list control allows each item to have a different height?

What control should I use to make a list of items display? I want to be able to adjust the height of the item so its height can take up more space then another item in the same control list of items.
I looked at listboxes, but you can't adjust the size of the items. I've considered making blank entries for placeholders to be grouped as the same item but would rather not if possible.
What this control is to be used for is to represent chunks of time from the beginning of the day (the top) to the end of the day (the bottom).
I looked at listboxes, but you can't adjust the size of the items
But you can. Set the DrawMode property to OwnerDrawVariable.
You of course need to tell it how tall each item needs to be, that requires implementing the MeasureItem event. And of course you need to draw it, so you fill up the space that you reserved with MeasureItem, that requires implementing the DrawItem event. You'll find an excellent example in the MSDN Library article for MeasureItem.
A custom panel does it like a charm:
public class AutoPanel : Panel
{
public AutoPanel()
{
AutoScroll = true;
}
private int _nextOffset = 0;
public int ItemMarginX = 5;
public int ItemMarginY = 5;
public void Add(Control child)
{
child.Location = new Point(ItemMarginX, _nextOffset);
_nextOffset += (child.Height + ItemMarginY);
Controls.Add(child);
}
}
Add it to your form and add items to it like this:
panel1.Add(new Button { Text = "smaller item", Height = 20 });
panel1.Add(new Button { Text = "medium item", Height = 23 });
panel1.Add(new Button { Text = "larger item", Height = 32 });

Silverlight binding to canvas, element location in list at element?

I have created a custom user control, which is basicly a ItemsControl with it's ItemsPanelTemplate set to canvas.
On the mainpage I bind a List<Element> to it, where Element is a custom class.
However, all the controls are placed right on top of eachother. A easy way to fix this ofcourse is by making the Canvas a WrapPanel but I'm not sure if this will colide with the ability of Drag & Drop on the control
So my question would be, is it possible to have a property in the model Element which checks on which position it is of a list, if it is in a list?
something like:
public class Element
{
public int positionInList { get { return (this.IsInList) ? this.ListPosition : 0; } }
}
Update
What I wish to accomplish is that when the elements are added to the canvas, they automaticly pick their spot by 2 properties (which will be bound to the Canvas.Left and Canvas.Top or something similar)
public double GetX { get { return 50 * (Element.PositionInList % 5); } }
public double GetY { get { return 50 * (Element.PosotionInList / 5); } }
Without manually having to set the element's position in the list.
When you place elements in a Canvas, you must specify the coordinates where the elements will be placed (left upper corner for each element). Maybe is better to derivate from Canvas a new class that will perform the placement for you and use this class for ItemsPanelTemplate. Also, Drag & Drop should work with WrapPanel but depends on your requirements.
A quick and non optimal sample:
class CustomCanvas : Canvas
{
private int mChildsNum = 0;
...
CustomCanvas()
{
// Track changes that appear in canvas when new
// children are added
this.LayoutUpdated += CanvasChangeTracker;
}
...
private void CanvasChangeTracker(object source, EventArgs e)
{
if ( this.Children.Count != mChildsNum )
{
// A new child was added.
// Update the coordinates for children
mChildsNum = this.Children.Count;
}
}
...
}
This example can be considerably improved.
You need a custom panel, so you can create a class and inherits from Panel this will implements two methods to override
protected override Size MeasureOverride(Size availableSize)
protected override Size ArrangeOverride(Size finalSize)
Which you must implement.
Here is an example:
http://www.codeproject.com/Articles/31537/Custom-Panel-in-Silverlight-Advanced-Canvas
If you have properties GetX and GetY, you can do the same as the accepted answer here:
Silverlight 3 - Data Binding Position of a rectangle on a canvas
I would just reply to your post, since this is not really my answer, but I guess I don't have enough rep to reply.

WinForms DataGridView - Full text display and dataSource update

Earlier today i was suggested in here to use a DataGridView to print messages that needed a individual mark as read.
I followed the suggestion, and with some reading online i managed to bind it to my message list with the following results after some tweaking.
alt text http://img237.imageshack.us/img237/3015/datagridview.jpg
Currently i have 2 issues, the first one is that i didn't find a way to resize the row height to display the full message, and the second one is that when the list is updated, the DataGridView doesn't display the modifications.
Any way to solve both problems? Or do i need to use something other than DataGridView, and in that case what should i be using?
Also, is there any way to urls contained in the message to become clickable and be opened in the default browser?
EDIT
More info in relation to the binding.
Basically i have a class variable inside the form, and i do the initial binding with a button.
private void button1_Click(object sender, EventArgs e)
{
list.Add(new Class1() { Message = "http://www.google.com/", Read = false });
list.Add(new Class1() { Message = "Message way too long to fit in this small column width", Read = false });
dataGridView1.DataSource = list;
}
I then have another button that adds some more entries just to test it, and i know the list is properly updated, but there are no changes in the dataGridView.
EDIT 2
If i wasn't clear before i need for the width to be fixed, and the cell height that contains the long text to be enlarged and display the text in 2 lines
have you checked the options in the EditColumn using smart tag ?
you can add column of type
DataGridViewLinkColumn, set its Text property to Message
Try removing any value from width
and height properties for a
column. In this way, it will set the
column size (cell) size according to
the data size.
hope this helps
I'll take a stab and see if I can help.
First off the row height. There are two DataGridView Methods called AutoResizeRow and AutoResizeRows which will adjust the height of the row to fit the contents.
Can you show us how you are binding your data to the DataViewGrid and how the data might be modified? That will help with the modifications not updating.
As for the link, unfortunately I can't seem to find an object which handles this sort of thing natively. Most likely you will first have to decide if the text going into the DataGridView is a link (using a Regular Expression, if you were me). Second, display it differently in the DataGridView (underline it, make it blue). Third, put a click event on it and when that cell is clicked handle that by throwing it out to a browser. I will look a little further into it though since this seems like a lot of work (and I will keep my fingers crossed that someone knows better than I do).
Regarding the list not updating; there are two issues;
To notice add/remove, you need list binding events. The easiest way to do this is to ensure you use a BindingList<YourClass> rather than a List<YourClass>.
To notice changes to individual properties (in this context) you'll need to implement INotifyPropertyChanged on your type:
public class YourClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
private string message;
public string Message
{
get { return message; }
set { message = value; OnPropertyChanged("Message"); }
}
public bool isRead;
[DisplayName("Read")]
public bool IsRead
{
get { return isRead; }
set { isRead = value; OnPropertyChanged("IsRead"); }
}
}
For an example showing binding that to a list:
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
BindingList<YourClass> list = new BindingList<YourClass>();
DataGridView grid = new DataGridView();
grid.Dock = DockStyle.Fill;
grid.DataSource = list;
Button add = new Button();
add.Text = "Add";
add.Dock = DockStyle.Bottom;
add.Click += delegate
{
YourClass newObj = new YourClass();
newObj.Message = DateTime.Now.ToShortTimeString();
list.Add(newObj);
};
Button edit = new Button();
edit.Text = "Edit";
edit.Dock = DockStyle.Bottom;
edit.Click += delegate
{
if (list.Count > 0)
{
list[0].Message = "Boo!";
list[0].IsRead = !list[0].IsRead;
}
};
Form form = new Form();
form.Controls.Add(grid);
form.Controls.Add(add);
form.Controls.Add(edit);
Application.Run(form);
}

How can I set the position of my datagrid scrollbar in my winforms app?

In my C# winforms app, I have a datagrid. When the datagrid reloads, I want to set the scrollbar back to where the user had it set. How can I do this?
EDIT: I'm using the old winforms DataGrid control, not the newer DataGridView
You don't actually interact directly with the scrollbar, rather you set the FirstDisplayedScrollingRowIndex. So before it reloads, capture that index, once it's reloaded, reset it to that index.
EDIT: Good point in the comment. If you're using a DataGridView then this will work. If you're using the old DataGrid then the easiest way to do that is to inherit from it. See here: Linkage
The DataGrid has a protected GridVScrolled method that can be used to scroll the grid to a specific row. To use it, derive a new grid from the DataGrid and add a ScrollToRow method.
C# code
public void ScrollToRow(int theRow)
{
//
// Expose the protected GridVScrolled method allowing you
// to programmatically scroll the grid to a particular row.
//
if (DataSource != null)
{
GridVScrolled(this, new ScrollEventArgs(ScrollEventType.LargeIncrement, theRow));
}
}
Yep, definitely FirstDisplayedScrollingRowIndex. You'll need to capture this value after some user interaction, and then after the grid reloads you'll want to set it back to the old value.
For instance, if the reload is triggered by the click of a button, then in the button click handler, you might want to have as your first line a command that places this value into a variable:
// Get current user scroll position
int scrollPosition = myGridView.FirstDisplayedScrollingRowIndex;
// Do some work
...
// Rebind the grid and reset scrolling
myGridView.DataBind;
myGridView.FirstDisplayedScrollingRowIndex = scrollPosition;
Store your vertical and horizontal scroll values into some variable and reset them.
int v= dataGridView1.VerticalScrollingOffset ;
int h= dataGridView1.HorizontalScrollingOffset ;
//...reload
dataGridView1.VerticalScrollingOffset = v;
dataGridView1.HorizontalScrollingOffset =h;
you can save scroll position with next code
int Scroll;
void DataGridView1Scroll(object sender, ScrollEventArgs e)
{
Scroll = dataGridView1.VerticalScrollingOffset;
}
and you can set scroll of dgv to same position after refresing, load dgv... with next code:
PropertyInfo verticalOffset = dataGridView1.GetType().GetProperty("VerticalOffset", BindingFlags.NonPublic |
BindingFlags.Instance);
verticalOffset.SetValue(this.dataGridView1, Scroll, null);
Just posted the answer on the link given by BFree
The DataGrid has a protected GridVScrolled method that can be used to scroll the grid to a specific row. To use it, derive a new grid from the DataGrid and add a ScrollToRow method.
C# code
public void ScrollToRow(int theRow)
{
//
// Expose the protected GridVScrolled method allowing you
// to programmatically scroll the grid to a particular row.
//
if (DataSource != null)
{
GridVScrolled(this, new ScrollEventArgs(ScrollEventType.LargeIncrement, theRow));
}
}
VB.NET code
Public Sub ScrollToRow(ByVal theRow As Integer)
'
' Expose the protected GridVScrolled method allowing you
' to programmatically scroll the grid to a particular row.
'
On Error Resume Next
If Not DataSource Is Nothing Then
GridVScrolled(Me, New ScrollEventArgs(ScrollEventType.LargeIncrement, theRow))
End If
End Sub
I used the answer by #BFree, but also needed to capture the first visible row in the DataGrid:
int indexOfTopMostRow = HitTest(dataGrid.RowHeaderWidth + 10,
dataGrid.PreferredRowHeight + 10).Row;
Even though this is an old question, Many of the solutions above did not work for me. What worked ultimately was:
if(gridEmployees.FirstDisplayedScrollingRowIndex != -1) gridEmployees.FirstDisplayedScrollingRowIndex = 0;

Categories

Resources