locked
Databinding of datagridrow button RRS feed

  • Question

  • I've added a button to the command bar of a data grid row and wired it to an execute method to do what I need.

    A column of buttons appears as expected and if I click on one of the buttons my functionality works as expected.

    What I want to do now is disable buttons depending on the value of a particular field in the same data row. Basically if the corresponding field is null, then disable the button.

    I thought the best way to achieve this would be to bind the isEnabledProperty of each button to the field and implement an iValueConverter.

    So far I'm adding the binding during the LoadingRow event of the parent grid, but I cant for the life of me work out the binding path. What I need is a path to the other field from the buttons context.

    If I use "Value" my iValueConverter always receives null as the object. I can only assume this is because the datacontext of the button is the name of the button (as shown in the designer) and not the EntityCollection(item).Field to which the other datagrid columns are bound.

    From what I've found online, the only other syntax for the binding path is to use "Screen", but I tried this in the format of Screen.MyEntityCollection(item).MyField but all I get out of that exercise was a "Catastrophic Failure Exception" on the line of code that tries to set the binding using that syntax.

    I know my code is working because if I set the path to a local screen property (eg. "Screen.MyProperty) it all works.

    I did consider adding a custom control (button) to the grid, so the datacontext was the field needed. Then the "Value" syntax should work. But that does not seem to be the right approach, because what if I then needed to bind the BackgroundProperty of the control to another field ?

    How do you get back to the parent row entity from a bound control's context (which is to a field set in the designer) and then select alternative fields of that entity ? 

     

    Saturday, January 4, 2014 8:24 AM

Answers

All replies

  • This code sets up a binding in the screen's Created event. The row value to be used is set in the new Binding object creation. In this sample it's bound to the BalanceDue value.

    partial void Leases_ForBuildingAndOrPartyListDetail_Created()
    {
        //Indicate balance status with text color
        const string CONTROLLeaseBalanceDue = "Leases_ForBuildingAndOrParty_SelectedItem_BalanceDue";
        IContentItemProxy leaseBalanceDue = this.FindControl(CONTROLLeaseBalanceDue);
        leaseBalanceDue.ControlAvailable += LeaseBalanceDue_ControlAvailable;
    }
    
    void LeaseBalanceDue_ControlAvailable(object sender, ControlAvailableEventArgs e)
    {
        //string controlType = e.Control.ToString();  //System.Windows.Controls.TextBlock
        System.Windows.Controls.TextBlock tb = (System.Windows.Controls.TextBlock)e.Control;
        Binding b = new Binding("Screen.Leases_ForBuildingAndOrParty.SelectedItem.BalanceDue")
        {
            Mode = System.Windows.Data.BindingMode.OneWay,
            Converter = new BalanceToForegroundColorConverter()
        };
        tb.SetBinding(System.Windows.Controls.TextBlock.ForegroundProperty,b);
    }
    

    Paul

    Monday, January 6, 2014 2:54 PM
  • Thanks for the suggestion Paul. I hadn't come across the Binding class, and after reviewing the docs it looks to be the ticket. It has a Source property which I can point to the datacontext of the row.

    However your code isn't applicable in my situation. As per my original post I have a column of buttons inside the datagrid. As soon as I try to use the .ControlAvailable event, the runtime tells me that it is "unavailable for controls in a collection"

    From what I can work out I need to set the binding to the button inside a DataGrid.RowLoaded event handler. From here I can get the proxy to the button via the FindControl method, but dont know how to get the actual button instance from here. 

    The SetBinding() method on the proxy would be the perfect solution if one of the method overloads was Setbinding(DependencyProperty, Binding), but for some reason that signature is not one of the 3 overloads....go figure ???

    I also can't use the fully qualified path (as in your example) to the available proxy.SetBinding() overloads, as I get the "Catastrophic Failure Exception", which I assume is because I'm in a collection.

    Any other suggestions ?


    Tuesday, January 7, 2014 1:56 AM
  • I have a similar binding to each row in a grid. Here's the code. It's not exactly the same as your situation, but maybe it helps.

    partial void Leases_ForBuildingAndOrPartyListDetail_Created()
    {
        //Indicate balance status with text color in the charges grid
        const string CONTROLChargeItemGrid = "LeaseChargesGrid";
        IContentItemProxy chargeItemGrid = this.FindControl(CONTROLChargeItemGrid);
        chargeItemGrid.ControlAvailable += ChargeItemGrid_ControlAvailable;
    }
    
    void ChargeItemGrid_ControlAvailable(object sender, ControlAvailableEventArgs e)
    {
        DataGrid grid = (DataGrid)e.Control;
        grid.LoadingRow += ChargeItemGrid_LoadingRow;
    }
    
    void ChargeItemGrid_LoadingRow(object sender, DataGridRowEventArgs e)
    {
        DataGridRow row = e.Row;
        Binding b = new Binding("BalanceDue")
        {
            Mode = System.Windows.Data.BindingMode.OneWay,
            Converter = new BalanceToBackgroundColorConverter()
        };
        row.SetBinding(DataGridRow.BackgroundProperty, b);
    }


    Paul

    Tuesday, January 7, 2014 8:19 PM
  • Yeah that is similar, to what I already have, but I need to get to a control in the row, and then apply the new Binding to it once I have the instance.

    Do you know anything about row templates ? I've been thinking that a template might be the way to do it. i.e. set up the binding rule in the template which then gets automatically applied to row when it is created. But I've got zero experience with using templates and how they work. 

    Wednesday, January 8, 2014 9:46 PM
  • I also don't have experience here. Maybe e.Row.FindName() or .GetTemplateChild would find the button?

    Paul

    Wednesday, January 8, 2014 10:23 PM
  • Here is an example where converter is not needed.

    http://social.msdn.microsoft.com/Forums/vstudio/en-US/e1984243-3576-4471-8004-d142cbf05041/grid-button-enabled-state-can-this-be-row-specific-?forum=lightswitch

    partial void SearchCities_Created()
    {
        int index = 0;
        foreach (City item in Cities)
        {
            this.FindControlInCollection("Btn", this.Cities.ElementAt(index)).IsEnabled = item.IsActive;
            index += 1;
        }
    }

    In this example you have a grid bound to Cities collection of City entities each having a Boolean property called IsActive.

    HTH,

    Josh


    • Edited by joshbooker Thursday, January 9, 2014 7:44 PM
    • Proposed as answer by joshbooker Friday, January 10, 2014 5:53 PM
    • Marked as answer by Ordinary Orange Monday, January 13, 2014 11:22 PM
    Thursday, January 9, 2014 7:40 PM
  • Thanks Josh, that does work so I'll award the answer because technically you have solved the primary question.

    While this approach is suitable for my current needs right now, a downside I can see from this technique is that the button will not change its enabled state if the referenced field content changed, because we are not using binding and involving iNotifyPropertyChanged.

    I was hoping someone might have worked out how to do this via binding, as that seems to be the better design choice.

    Monday, January 13, 2014 11:22 PM
  • Binding would be the better design choice, as you mentioned, since you'll need to execute the loop method at various times to keep the UI in sync. 

    Did you try using the SetBinding method inside the foreach loop?

    This seems sample seems to use binding on grid row:

    http://code.msdn.microsoft.com/Control-Colors-of-DataGrid-fa23c265/sourcecode?fileId=68303&pathId=652224532

    The RowLoading code seems to get the column value using a grid variable which is set in ControlAvailable.  Not sure but that may be the trick(?)

    HTH,

    Josh

    • Edited by joshbooker Tuesday, January 14, 2014 3:20 AM
    Tuesday, January 14, 2014 3:13 AM
  • ick, all that code just to implement some specific binding.....

    I see what the code is doing, but in the example it is the DataGridCell instance that is being located and bound. I need to get the Button in the cell and apply binding to that.

    I'l give it a go, but surely there is a better way...


    Tuesday, January 14, 2014 5:10 AM
  • I believe the Cell Content is the button, so

    instead of:

    changeCell = dg.Columns[2].GetCellContent(row).Parent as DataGridCell; 
    if (changeCell != null
        changeCell.SetBinding(...

    this:

    changeButton = dg.Columns[2].GetCellContent(row) as Button;
    if (changeButton != null
        changeButton.SetBinding(...

     

    HTH,

    Josh

    Tuesday, January 14, 2014 12:27 PM
  • I've finally just got time to follow this up.

    It appears the Cell Content is a ContentItemPresenter not a Button.

    A cast just throws an error.

    I've tried to understand how the ContentItemPresenter works, as it is showing a button on the screen, but I still can't see any way to access the button instance....

    Saturday, January 25, 2014 12:46 PM
  • Try the ContentPresenter.Content property:

    changeButton = dg.Columns[2].GetCellContent(row).Content as Button;
    if (changeButton != null
        changeButton.SetBinding(...

    http://msdn.microsoft.com/en-us/library/system.windows.controls.contentpresenter.content(v=vs.110).aspx

    Saturday, January 25, 2014 12:57 PM
  • Thanks for persisting with me Josh but the object from the GetCellContent(row) method is a ContentItemPresenter, not a ContentPresenter.

    I've found some other posts that suggest similar to what you have, but the examples are related to silverlight. I think lightswitch is doing something slightly different.

    I've put a breakpoint in my code, and via the watch, explored all of the possible Properties on the ContentItemPresenter and and even the DataGridRow (parent object), but I can't locate a reference to the actual Button instance.

    I have also played with other columns in the grid, where the content of the datagridcell is a TextBox and it is the same story here. I Get a ContentItemPresenter, and I can see that it is rendering a Textbox or Button, but I just cant see how to get the actual control instance.....

    I think I'll post another new thread on the Topic of ContentItemPresenters as there is zero documentation on them.

    Sunday, January 26, 2014 5:09 AM
  • Yeah, there's zero documentation on a lot of LS. You might try the extensions forum too. From the few posts I have read, there appears to be a contentitem property of contentitempresenter. You could try casting ContentItemPresenter.ContentItem.View Ps did you try setting the is enabled on the grid cell? Curious whether the property is inherited by the child button.
    • Edited by joshbooker Sunday, January 26, 2014 6:43 AM
    Sunday, January 26, 2014 6:36 AM
  • No good with the casting. Error city.

    IsEnabled property does not inherit either.

    New thread here which has a broader scope of application.

    Thanks very much for all your help and suggestions, much appreciated.

    Sunday, January 26, 2014 9:01 AM
  • For anyone following this but needing a proper solution, see this thread
    Friday, February 14, 2014 11:51 AM