Hello,
I am having trouble trying to get UltraControlContainerEditor and my custom control to work properly. I have a couple of issues.
I have included a small sample project.
Hi Jeff,
I ran your sample and expanded the child band and I can see the that row is not tall enough to display the entire RenderingControl. Is that the issue you are seeing?
The grid tries to AutoSize the row and in doing so it will call into the editor for it's Size, which in turn calls into the GetPreferredSize method of the RenderingControl. So what you need to do in order to make the sizing work is to override GetPreferredSize on your ctlRecurrenceWeekly control and return the appropriate size that you want the control to be.How you calculate that size is up to you, and in some cases, you might want to take the font or the contents of the cell into account. But just as a test, I added this code and that worked fine:
Public Overrides Function GetPreferredSize(proposedSize As Size) As Size Return New Size(538, 166) End Function
Of course, you probably don't want to hard-code the numbers here because it vary by system resolution, DPI, font, etc.
As for the updating, that's a little trickier. The way it typically works is that your control uses a value type. So when you set the value on your control, you would send a notification that the property value changed. But in this case, you are not setting the value, you are just setting properties on an reference type object. In other words, the instance of the object doesn't change, only properties ON that object are changing.
Also.. since your sample has only one child row and you are creating a new editor and a new set of controls for each row, it's really hard to tell that the grid is not recognizing the change in the row. The only way to know that is that you can see the RowSelector is not changing to a pencil icon when you change something.
Anyway... editor is looking for a change notification from the EditingControlProperty - in this case "Recurrence". And your control is not sending one.
What you need to do here is to send a PropertyChanged notification for the "Recurrence" property any time one of it's child properties changes. You are already hooking PropertyChanged on the object itself, so you just need to bubble up this notification to the control.
First, implement INotifyPropertyChanged on your control:
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Then send a notification when something changes:
Private Sub Recurrence_PropertyChanged(ByVal sender As Object, ByVal e As PropertyChangedEventArgs) Me.UpdateUI() 'Dim e2 As PropertyChangedEventArgs = New PropertyChangedEventArgs("Recurrence") 'RaiseEvent PropertyChanged(Me, e2) End Sub
I updated your sample with this code and now when I clicked on a checkbox inside the cell, like Monday, I see the row selector image turn into a pencil, indicating that the row is dirtied.
So that seems to fix both problems you are describing.
In addition, I noticed that you have to click twice for the checkboxes to respond. This is because the first click puts the cell into edit mode and then the second one changes the checkbox. But since your Editing and Rendering controls are the same, you can fix this like so:
containerEditorScheduleType.EnterEditModeMouseBehavior = EnterEditModeMouseBehavior.EnterEditModeAndClick
Finally... there doesn't seem to be any need to create an editor for every cell. Maybe you just did that as an attempted workaround, but it's very inefficient and you are going to end up creating a lot of controls. You'd be much better off just creating the editor and attaching it to the column, rather than the cell.
Hi Mike,
First thank you for the updated sample project. I do have a couple of questions to wrap this up.
The AfterCellUpdate event does not seem to get fired when changing the value only the CellChange event. Is there a reason?
When you mention,
Mike Saltzman said:Finally... there doesn't seem to be any need to create an editor for every cell. Maybe you just did that as an attempted workaround, but it's very inefficient and you are going to end up creating a lot of controls. You'd be much better off just creating the editor and attaching it to the column, rather than the cell.
Jeff said:The AfterCellUpdate event does not seem to get fired when changing the value only the CellChange event. Is there a reason?
That's odd. My guess is that the AfterCellUpdate is tied to the data source notifications, and since the cell still contains the same instance of the Recurrence object, it doesn't recognize that anything has changed.
One way to get around this would be to have your control return a new instance of the Recurrence class any time it gets modified, instead of just altering properties on the same instance.
Another option might be to override Equals and GetHasCode on the Recurrence class so that it only return equal when the instance has exactly the same values instead of doing a reference comparison.
Jeff said:When you mention, Finally... there doesn't seem to be any need to create an editor for every cell. Maybe you just did that as an attempted workaround, but it's very inefficient and you are going to end up creating a lot of controls. You'd be much better off just creating the editor and attaching it to the column, rather than the cell. what would be the best approach when each child row can have a different recurrence type? So in this case it was Weekly but the next child row could be using ctlRecurrenceMonthly. Would be best to create the UltraControlContainerEditor on the form and attach it to the column in InitializeLayout. Then use InitializeRow to create the Recurrence controls and attach them to the UltraControlContainerEditor?[/quote] Well... if you do need to have different editing and rendering controls in each cell, then the best thing to do would be to create the UltraControlContainerEditor and it's rendering and editing controls once. Then just assign the appropriate UltraControlContainerEditor in InitializeRow. That way you create 2 components and 4 controls, instead of 1 component and two controls for each row.
Well... if you do need to have different editing and rendering controls in each cell, then the best thing to do would be to create the UltraControlContainerEditor and it's rendering and editing controls once. Then just assign the appropriate UltraControlContainerEditor in InitializeRow. That way you create 2 components and 4 controls, instead of 1 component and two controls for each row.
Mike Saltzman said: One way to get around this would be to have your control return a new instance of the Recurrence class any time it gets modified, instead of just altering properties on the same instance. Another option might be to override Equals and GetHasCode on the Recurrence class so that it only return equal when the instance has exactly the same values instead of doing a reference comparison.
I have tried to return a new instance of the Recurrence class and no matter what I try I can only get the AfterCellUpdate to fire on the very first time a value gets changed. After that the event doesn't fire again.
When I try the option to Override Equals and GetHashCode nothing happens for me.
Bingo!
Awesome Mike, thanks a bunch. I didn't think of that.
I went back and looked at my sample again and I see the problem. I was replacing the member variable with a new instance. But this means the control is still hooking the Changed event of the old instance and not the new one. So a better solution is to set the property so that the events get hooked/unhooked:
Private Sub Recurrence_PropertyChanged(ByVal sender As Object, ByVal e As PropertyChangedEventArgs) Me.Recurrence = New clsRecurrence( mclsRecurrence.ScheduleID, mclsRecurrence.StartDate, mclsRecurrence.EndDate, mclsRecurrence.RunTime, mclsRecurrence.RecurrenceType, mclsRecurrence.Interval, mclsRecurrence.StartingTime, mclsRecurrence.EndingTime, mclsRecurrence.DayOfMonth, mclsRecurrence.Sunday, mclsRecurrence.Monday, mclsRecurrence.Tuesday, mclsRecurrence.Wednesday, mclsRecurrence.Thursday, mclsRecurrence.Friday, mclsRecurrence.Saturday, mclsRecurrence.MonthOfYear, mclsRecurrence.Instance ) Me.UpdateUI() Dim e2 As PropertyChangedEventArgs = New PropertyChangedEventArgs("Recurrence") RaiseEvent PropertyChanged(Me, e2) End Sub
Thanks for the reply.
That is what I had tried only fires on the first change after that the AfterCellUpdate event doesn't fire again for the same control instance.
I have changed to use CellChange event for instances of these custom controls.
It looks like my Equals/GetHashCode theory won't work. but creating a new object does.
I tried this out in a very limited test and it works fine for me.
All I did was change the Recurrence_PropertyChanged like so:
Private Sub Recurrence_PropertyChanged(ByVal sender As Object, ByVal e As PropertyChangedEventArgs) Me.UpdateUI() Me.mclsRecurrence = New clsRecurrence( mclsRecurrence.ScheduleID, mclsRecurrence.StartDate, mclsRecurrence.EndDate, mclsRecurrence.RunTime, mclsRecurrence.RecurrenceType, mclsRecurrence.Interval, mclsRecurrence.StartingTime, mclsRecurrence.EndingTime, mclsRecurrence.DayOfMonth, mclsRecurrence.Sunday, mclsRecurrence.Monday, mclsRecurrence.Tuesday, mclsRecurrence.Wednesday, mclsRecurrence.Thursday, mclsRecurrence.Friday, mclsRecurrence.Saturday, mclsRecurrence.MonthOfYear, mclsRecurrence.Instance ) Dim e2 As PropertyChangedEventArgs = New PropertyChangedEventArgs("Recurrence") RaiseEvent PropertyChanged(Me, e2) End Sub
So basically, every time any property changes (or at least the ones that fire the notification) I "clone" the mclsRecurrence object so it's a new instance with the same properties. Not very efficient, but that's the way it has to work in order for the event to recognize that the value of the cell has changed.