6

I'm developing a -based application and I'm currently struggling to find the best way of supporting a recurring scenario in it.

This app uses grids a lot. There are a bunch of features I want in it that are meant to be used in grids, such as drag & drop, multiple row edition, conditional row coloring, etc. But at the same time, I want my data objects to derive from a specific, data-oriented class that is unaware of any UI context and could be used by a single form, with standalone editors.

Therefore, I'd like to have a DataGridViewModel class that will in turn expose two collections: Rows as DataGridRowViewModel and Columns as DataGridColumnViewModel. The row view model would expose a RowData property which will be the actual data object, while the column view model would have a PropertyDescriptor (from System.ComponentModel namespace) that will indicate which data object field the column is meant for.

You can think of the DataGridRowViewModel as a container for the "real" row object.

The goal is to allow separation of concerns between grid-specific functionalities (controlling row height, color, selection, etc) and data-specific functionalities, so that one doesn't need to know about the other. Ideally, a third component will be aware of both and ensure proper communication between the two layers (observing a data field and change the row color under condition x or y, for example).

Does this approach makes any sense? What kind of complications should I expect out of this (binding issues, performance and the likes)?

  • 1
    I have done this. Be careful of some caveats. A DataGrid has some cool features like automatically being able to sort by clicking on column headers, and if you want to track edits to a cell after its been part of a sorted grid, it can get complicated. Depending on what your binding target it, the DataGrid wraps that binding target in a sortable view of its own to bind to. – Scott Whitlock Sep 11 '15 at 18:20
  • I know this is kind of an old question, but I think you've missed an important concept about the VM in MVVM. The View Model is meant to be a Controller, not a DTO. It's okay for it to know about the view because the view is its job. The VM should delegate to an object that doesn't. – RubberDuck Jul 8 '16 at 11:32
  • @RubberDuck This is exactly what I'm trying to do here, I want a view model for an actual, visual row object. – Crono Jul 8 '16 at 15:48
2

I've been working with grids for a while and had simmilar idea and scenarios. There is a flaw in this approach: you want many features on your grid, but this means you'll have many very different kinds of data in it. So your grid will have to visualize and edit all this data.

This isn't a problem by itself, but, this means that grid will become "God Object" itself. There is a different approach to solve this: make grid as a "thin" container. I mean don't think of a grid as a cells with text.

Think of it as a layouts.

Then:

  • Grid is really a list of rows. Each row can have cells, so you'll have to create some mechanism to sync columns' width, but some rows may be special (grouping, totals, details, seprators, spans ...)
  • Each cell is really a panel for other components: it can be quite complex, so it won't be just text in it: there can be an edit with 2 buttons and an image.
  • This way grid and row doesn't know about what the cell is, except that it's size and location.
  • This all means that binding data to cells isn't binding data to two dimensional array of values, but rather binding an array of rows to an array of objects.
  • Rows should handle binding cells to object's fields or to some computed values over some fields.
  • Cells should pass bindings to their child components
  • And the components inside cells (texboxes in simplest and most common cases) will finally handle the binding.

The approach I proposed is quite complex to implement though. Maybe you don't need such complex cases. Also be warned that having this many components requires optimizations (specifically in simple and most common cases). But in the result you can have a swiss-knife solution for all scenarios with grids.

Comparing to your proposial it have some benefits (and your proposial have limitations in this areas): it can take data from deep fields inside your objects, it can have computed values, you don't have to convert your objects to "RowData@, it can handle several hungry components in one cell.

For your question "Is this really ok?" - yes it is. There are several big frameworks do grids your way, from what comes to my mind right now: Qt have this way of binding. So your approach is a well known good practice anyway too whith some known problems and limits though.

0

We're aiming to do something similar in our web-based UI project, so what you're trying to achieve makes perfect sense to me. The view model should be flat, so binding should be straightforward to achieve.

In our case, we found that we are using lists of data all over our project, but there were only three use cases for the list: "select one of these items" (single select button per row), "select one or more of these items" (checkboxes and a submit button), and "manage this list of items" (Add New button at the top, Edit and Delete buttons per row). In any case, the columns of data are read only.

We're working to develop a component with a constructor that takes a parameter to describe which type of list is needed and a view model object that has 1-4 columns of display data plus an Id as a reference. The component will figure out what the list should look like from there. We made a custom annotation class to decorate the view model with:

[System.AttributeUsage(System.AttributeTargets.Property)]

public class ListViewColumn : System.Attribute
{
    private string _columnName;
    public int ColumnOrder;
    public string FormatString;

    public ListViewColumn(string columnName)
    {
        _columnName = columnName;
        ColumnOrder = 1;
        FormatString = null;
    }
}

Usage:

    [ListViewColumn("Option Code", ColumnOrder = 1)]
    public string OptionCode { get; set; }

    [ListViewColumn("Version", ColumnOrder = 2)]
    public float Version { get; set; }

    [ListViewColumn("Description", ColumnOrder = 3)]
    public string Description { get; set; }

If you were generating a list for the "Select One" metaphor (a class attribute can drive that), this would result in a list that looked similar to the following:

        Option Code   Version   Description
Select  101-10G       4.2       Whatever this thing is
Select  234-567       4.35      A clear description of this other thing
Select  456-987       2.9       Something completely different

Using the same class to generate a "Manage" list would produce the following:

                                                                 Add New
Option Code   Version   Description
101-10G       4.2       Whatever this thing is                   Edit | Delete
234-567       4.35      A clear description of this other thing  Edit | Delete
456-987       2.9       Something completely different           Edit | Delete
  • I'm not sure I understand. Is your component (the one with the constructor receiving the viewmodel) abstract? You derive from it and you decorate the derived class with multiple ListViewColumn attributes? – Crono Aug 11 '15 at 13:08
  • That's my understanding of the situation. Unfortunately (or fortunately, depending on your perspective), I'm not a UI developer; my experience is far further down the stack. Also my role on the project is Architect, so identifying the pattern and telling the development team that we need something that fits the pattern is about as far as I can go. – J.D. Ray Aug 11 '15 at 15:37
  • Okay but that's your code, right? Therefore you have an idea on how it will be used? Have some sample code of that? – Crono Aug 11 '15 at 17:20
  • I made some edits to explain myself (or, at least, my idea). – J.D. Ray Aug 12 '15 at 17:34
  • 1
    In my scenario I wanted one grid row model that would wrap a non-persistent data model which would later be passed to a persistent model for db writing operations. I wanted the grid row model to be unique and present the data model as the row's "content". This allows a grid "row" to be exactly that: a distinct, visual rectangle inside a scrollable display, with a configurable height, fore/back color and the likes. The tricky part is that the "content" data model must not be made aware that it's being used in a grid context. Decorating it as such would therefore break the concept. – Crono Aug 12 '15 at 23:07
0

What kind of complications should I expect out of this

There is a very nice consistency in our app's UI behavior that is deep. And it comes from the basic building blocks of which gridview is one. At this fundamental level someone did a very nice job with the design. These very basic building blocks are the base(s) (and basis!) for less abstract, general functional area, base classes.

The keys to a very esoteric yet useful set of "legos" are the design of the concrete builders and programmer training.

You must train your coders how to work with these artifacts. It simply is not obvious how our abstractions are built up; it seems like you start with a window-base with nothing but a couple of buttons, a generic gridview (with no actual designer) and then a big bang happens. A window with 3 drop-downs that interactively populate 2 grids that have filtering, selection, and possibly mutual interaction.

Your builder code must be reasonably designed; the simplest of OO application often is enough but when your coders do not have an appreciation of the existing design you're screwed. And honestly most coders I've worked with are bad at OO. I hope you have code reviews. The deeper the code in the building process, the more painful are its flaws.

For example we have a class that is metadata for building concrete gridviews. It is nothing but string properties - a true DTO poster child. One is a CSV list of DB table column names (the 1st is the default sort, who knew?), another is the gridview headings, some are SQL snippets. Some values are quoted, some not. Some single quotes, some double. And where's the rest of the SQL?! Some properties were used together but not always. I understood nothing until I read lots of code and did lots of debugger tracing. What idiot doesn't know what a constructor is for? Method parameters? Crap. Client classes are littered with procedural code picking apart these values. Bad and absent OO design makes itself felt far into the call stack.

I ended up writing lots of extension methods for this class for use in new functionality. With lots of unit tests! I did not dare re-write the class because it was being used in about 100 places, and was pretty deep inside the building process

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.