tags:

views:

889

answers:

3

Can anyone tell me if it is possible to create and add data to an unbound WPF Toolkit datagrid.

If it is in fact possible to do, can someone please provide an example of the following:

Programmatically: create a datagrid create a datagridtextcolumn add the datagridtextcolumn to the datagrid create a datagridrow set the text of the datagridtextcolumn for the row to "test" add the datagridrow to the datagrid

I would like to create a datagrid that is unbound. My reason is that i would like to create a template column with multiple controls of different types and numbers - 2 checkboxes, 4 radiobuttons, 5 checkboxes, etc. - which are dynamically added at runtime and since the type and number are unknown cannot think of a way to databind them. I am happy to work unbound.

Thank you in advance!

[Edit: I have not yet found an appropriate answer to this question and have started a bounty]

+1  A: 

I've never used a grid without binding it to something.

BUT looking at the underlying reason, that can be solved while still using databinding.

For example: If you use a ViewModel class for you object that the grid is being bound to, then you can make an aspect of that class be the visibility setters for the various controls.

In the object, you'd have this:

Public ReadOnly Property CheckboxAVisibility As Windows.Visibility
   Get
     ' Do Logic 
     Return Visiblity 
   End Get
End Property

And in the XAML you'd do:

<CheckBox IsChecked="{Binding IsBoxAChecked}" Visibility={Binding CheckboxAVisibility}" />

This also makes things easier, as you'll be able to modify the visibility of various controls by modification of other controls within the row (ex: unchecking CheckboxA causes RadioButtons B, C & D to appear).

A second option would be to list just "Header" information in the grid, and when the user double clicks a row, you would display an editor with the editable aspects in a secondary panel or window (similar to how MVC editing works).

edit due to comment

Here's another suggestion, rather than a datagrid, use a StackPanel. For each row you need, you can add a grid panel or stackpanel or something similar, created at runtime according to your rules. For example:

XAML:

<StackPanel Name="stkItems" Orientation="Vertical" />

Code:

Public Sub AddItems(dt as DataTable)
   For Each row As DataRow in dt.Rows
     Select Case row("Which")
          Case 1:
             Dim i As New Checkbox
             i.Content = "Foo"
             i.IsChecked = row("Content")
             stkItems.Children.Add(i)
          Case 2:
              Dim i as New TextBox
              i.Text = row("Content")
              stkItems.Children.Add(i)
     End Select
   Next
End Sub

This is highly simplified, and you could do things such as have predefined user controls that you could use, or just build a bunch of controls into a programatically defined container, and then add that to the StackPanel

Stephen Wrighton
The ViewModel approach is something I've considered. However, I cannot figure out how to get over the fundamental hurdle of not knowing for any given row, what type of controls I will have and how many I will have. Hard coding a certain number on there and setting their visibility is not possible.
Jason Irwin
You might be happier eschewing the datagrid then--look at my edits
Stephen Wrighton
Unfortunately that is what I had to do before posting this question. I was hoping there was another option as some of the built-in datagrid functionality is really nice!
Jason Irwin
+1  A: 

You can use UserControl that will create desired controls.

Window1.xaml (snippet)

<dg:DataGrid ItemsSource="{Binding}" CanUserAddRows="False" AutoGenerateColumns="False">
    <dg:DataGrid.Columns>

       <dg:DataGridTemplateColumn Header="Test" MinWidth="100">
            <dg:DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <my:ControlA Foo="{Binding}"></my:ControlA>
                </DataTemplate>
            </dg:DataGridTemplateColumn.CellTemplate>
        </dg:DataGridTemplateColumn>

    </dg:DataGrid.Columns>
</dg:DataGrid>

Window1.xaml.cs

public partial class Window1 : Window
{
    List<Foo> _items = new List<Foo>();

    public Window1()
    {
        InitializeComponent();

        _items.Add(new Foo { CheckBoxCount = 2, TextBoxCount = 1 });
        _items.Add(new Foo { CheckBoxCount = 3, TextBoxCount = 0 });

        DataContext = _items;
    }
}

ControlA.xaml

<UserControl x:Class="Demo.ControlA"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"&gt;
    <StackPanel x:Name="_placeHolder">
    </StackPanel>
</UserControl>

ControlA.xaml.cs

public partial class ControlA : UserControl
{
    public ControlA()
    {
        InitializeComponent();

        Loaded += new RoutedEventHandler(ControlA_Loaded);
    }

    void ControlA_Loaded(object sender, RoutedEventArgs e)
    {
        if (Foo != null)
        {
            for (int it = 0; it < Foo.CheckBoxCount; it++)
                _placeHolder.Children.Add(new CheckBox());

            for (int it = 0; it < Foo.TextBoxCount; it++)
                _placeHolder.Children.Add(new TextBox());
        }
    }

    public static readonly DependencyProperty FooProperty =
        DependencyProperty.Register("Foo", typeof(Foo), typeof(ControlA));

    public Foo Foo
    {
        get { return (Foo)GetValue(FooProperty); }
        set { SetValue(FooProperty, value); }
    }
}

Foo.cs

public class Foo
{
    public int TextBoxCount { get; set; }
    public int CheckBoxCount { get; set; }
}

Hope this helps.

P.S. It should be possible to refactor DataGridTemplateColumn into separate UserControl.

alex2k8
Your answer looks pretty good. I'm going to run some quick tests and, if all goes well, the bounty is yours! Thanks for your help!
Jason Irwin
Thank you for your help Alex! The bounty is yours.
Jason Irwin
Glad this helped you.
alex2k8
A: 

I have created a simple DataGridUnboundedColumn class that is derived from DataGridBoundColumn and can be used to supply custom text of FrameworkElement for the cell.

You simply subscribe to the CellFormating event and set the CellElement in the EventArgs to the custom element that will be show. It also possible to set just CellText in the EventArgs - in this case the TextBlock with the CellText will be shown in the Grid.

The following examples shows how to use it:

XAML:

<dg:DataGrid Name="DataGrid1" AutoGenerateColumns="False">
    <dg:DataGrid.Columns>
        <dg:DataGridTextColumn Header="Name" Binding="{Binding Path=Name}"/>
        <myColumn:DataGridUnboundedColumn x:Name="AddressColumn" Header="Address" />
    </dg:DataGrid.Columns>
</dg:DataGrid>

CODE:

   public MyPage()
    {
        InitializeComponent();

        AddressColumn.CellFormating += new UnboundedColumnEventHandler(AddressColumn_CellFormating);
    }

    void AddressColumn_CellFormating(object sender, UnboundedColumnEventArgs e)
    {
        IPerson person;

        person= e.DataItem as IPerson;

        if (person!= null)
            e.CellText = string.Format("{0}, {1} {2}", person.Address, person.PostalCode, person.City);
    }

The DataGridUnboundedColumn implementation is here:

class DataGridUnboundedColumn : DataGridBoundColumn
{
    public event UnboundedColumnEventHandler CellFormating;

    public DataGridUnboundedColumn()
    {
        this.IsReadOnly = true;
    }

    protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem)
    {
        return null;
    }

    protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
    {
        FrameworkElement shownElement;
        UnboundedColumnEventArgs eventArgs;

        if (CellFormating == null)
            return null;


        eventArgs = new UnboundedColumnEventArgs(cell, dataItem);

        // call the event
        CellFormating(this, eventArgs);

        shownElement = null;

        // check the data set in the eventArgs
        if (eventArgs.CellElement != null)
        {
            // show the set eventArgs.CellElement
            shownElement = eventArgs.CellElement;
        }
        else if (eventArgs.CellText != null)
        {
            // show the CellText in TextBlock
            TextBlock textBlock = new TextBlock();
            textBlock.Text = eventArgs.CellText;

            shownElement = textBlock;
        }
        else
        {
            // nothing set
        }

        return shownElement;
    }
}

public delegate void UnboundedColumnEventHandler(object sender, UnboundedColumnEventArgs e);

public class UnboundedColumnEventArgs : EventArgs
{
    public DataGridCell Cell { get; set; }
    public object DataItem { get; set; }

    /// <summary>
    /// The subscriber of the event can set the CellText.
    /// In this case the TextBlock is used to display the text.
    /// NOTE that if CellElement is not null, the CellText will not be used but insted a CellElement will be shown
    /// </summary>
    public string CellText { get; set; }

    /// <summary>
    /// The subscribed can set the FrameworkElement that will be shown for this cell.
    /// If the CellElement is null, the CellText will be used to show the TextBlock
    /// </summary>
    public FrameworkElement CellElement { get; set; }

    public UnboundedColumnEventArgs()
        : base()
    { }

    public UnboundedColumnEventArgs(DataGridCell cell, object dataItem)
        : base()
    {
        Cell = cell;
        DataItem = dataItem;
    }
}
Andrej Benedik