Question Expose properties from an element that is inside a UserControl?

muik

Member
Joined
Dec 3, 2009
Messages
13
Programming Experience
3-5
Hi,

I have created the following UserControl (simplified to the bare minimum for clarity):

<UserControl x:Class="StealthComboBox">
<Grid>
<Label Name="lblHeader" />
<Label Name="lblcboBoxValue" />
<ComboBox Name="cboBox" />
</Grid>
</UserControl>

I would like the users of my UserControl to be able to add items to the ComboBox through code.

I have tried a few things already by adding code to the StealthComboBox class, here are 2 examples:


Public Items as ItemCollection = cboBox.Items
Even though the UserControl builds fine, trying to add it to a project gives me the error "Could not create an instance of type:StealthComboBox"​


Public DropDownList as ComboBox = cboBox
The UserControl builds fine and I could add it to my project - it displays fine and Intellisense shows the ComboBox properties fine. However the "DropDownList" item is actually "Nothing": it does not reference to the cboBox inside my UserControl and any attempt to use it gives an NullReferenceException.​


Is there a simple way to directly expose the ".Items" and ".SelectedItem" properties and methods in a UserControl? I know that I can create home-made functions in my "StealthComboBox" class that will query the ComboBox values or use its .Items.Add function, like this:

Public Function Add(obj_ as Object) as Integer
Return cboBox.Items.Add(_obj)​
End Function

Public Function SelectedItem() as Object
Return cboBox.SelectedItem()​
End Function

This works pretty well, actually. But I'm mostly wondering if there's a more conventional way - one that would give my UserControl the full interface of a ComboBox instead of me "handpicking" the ones I think I will need?

And a more advanced question: what if I want a UserControl with 2 ComboBox items that must be exposed to the user? Isn't there a way to create some kind of "UserControl.List1" and "UserControl.List2", where each of them acts on a separate ComboBox list? (i.e. UserControl.List1.Items.Add and UserControl.List2.Items.Add)

Thanks for the help!
 
To expose the Items collection you have to do something along these lines:
VB.NET:
Public Property Items() As ObjectCollection
  Get
    Return ComboBox.Items
  End Get
  Set (ByVal Value As ObjectCollection
    ComboBox.Items = ObjectCollection
  End Set
End Property
And to give the user access to the SelectedItem your code's correct:
VB.NET:
Public Function SelectedItem() as Object

    Return cboBox.SelectedItem()

End Function
 
If you don't want to change the functionality mirroring those properties is usually what you do. That means using the same signature and get/set the internal objects property. Take for example Items property, when you look it up in help you get this page (this property is inherited from ItemsControl class) ItemsControl.Items Property (System.Windows.Controls) At top of page you see the declaration:
VB.NET:
<BindableAttribute(True)> _
Public ReadOnly Property Items() As ItemCollection
If you paste this and press Enter you get the full property body:
VB.NET:
<BindableAttribute(True)> _
Public ReadOnly Property Items() As ItemCollection
    Get

    End Get
End Property
Then you fill in the code to get the items from the internal Combobox control:
VB.NET:
Return CB.Items
 
Thanks for the quick replies!

However, none of your suggestions will retain the full functionalities of the ComboBox. It does work fine and having the ObjectCollection exposed is better functionality than a single link to the .Add method, and is enough for what I need to do (but so was my original code). Imagine a DataGrid - you will want to keep a LOT of the features it has and you probably don't want to relink everything manually...

I thought of something overnight though, and I just tried it and it works perfectly. It also answers my "what if you need 2 ComboBox to retain full functionality" question.

Basically, i was very close when I exposed a public variable that would link to my cboBox:

VB.NET:
Class StealthComboBox
    Public myBox as ComboBox = cboBox
End Class

For some reason, the above code builds correctly but gives a "NullReferenceException" when trying to access the myBox reference.

By changing the variable to a readonly property (this is what i dreamed about last night...):

VB.NET:
Class StealthComboBox
    Public Readonly Property myBox() As ComboBox
        Get
            Return cboBox
        End Get
    End Property
End Class

...not only it still builds correctly, but accessing to ".myBox" exposes the whole ComboBox members. They retain full functionality and even though the ".myBox" reference is readonly (which is what i want, obviously), any property within the ComboBox retains their original read/write settings.

If I would create a UserControl with, let's say, 2 ComboBox: one with Country and the other one with State, the easiest way to let the code access both of them would be:

VB.NET:
(XAML)
<UserControl Name="CountryStateSelector">
    <Grid>
        <ComboBox Name="_CountrySelector" />
        <ComboBox Name="_StateSelector" />
    </Grid>
</UserControl>

(Code-Behind)
Class CountryStateSelector
    Public Readonly Property CountrySelector() as ComboBox
        Get
            Return _CountrySelector
        End Get
    End Property

    Public Readonly Property StateSelector() as ComboBox
        Get
            Return _StateSelector
        End Get
    End Property
End Class

Then in my application:

VB.NET:
(XAML)
<my:CountryStateSelector Name="LocationSelector" />

(Code-Behind)

Public Sub somesub()
    LocationSelector.CountrySelector.Items.Add("USA")
    LocationSelector.CountrySelector.Items.Add("Canada")
    LocationSelector.StateSelector.Items.Add("Boston")
    LocationSelector.StateSelector.Items.Add("Quebec")
End Sub

Now I can access the whole combobox features with very minimal code.

The only drawback of this approach is that the combo box still isn't reachable through XAML so I can't use databinding. But for what I need to do, this is sufficient. I guess I could easily map a bindable property to the objectcollection afterward if i need databinding.

yay!
 
That combobox is already exposed in that fashion (Friend access level); you can write LocationSelector.cboBox to access it. This actually something one usually want to avoid to protect the internal objects, and outside assembly it is. More so exposing properties on the UserControl itself allow Designer support, you can for example set the usercontrol Items in Designer with the property I posted. From what I see that is also possible by exposing the control as regular property (not ReadOnly) in WPF, then you get a sublevel in Properties window for all the internal controls properties, IMO this is not desirable. Designer/Design-time support is of great importance and should be a main consideration when developing custom controls for a visual development environment.
 
That combobox is already exposed in that fashion (Friend access level); you can write LocationSelector.cboBox to access it. This actually something one usually want to avoid to protect the internal objects, and outside assembly it is.

I cannot seem to access cboBox from an application that references to my UserControl if I don't declare it public within my UserControl project. So "LocationSelector.cboBox" is not something that can be accessed if I don't specifically create a public that ties to it - am I missing something?

For the rest, I agree that your approach seems more conventional and probably more useful for designers. However for the sake of these usercontrols, it's not meant to be distributed and I'm the designer - they're mainly just a way for me to quickly place elements that are redundant in my application in an effort to lighten my XAML code that was beginning to be hard to maintain due to a "Tabs" apporoach.

Thanks a lot for the help and explanations - I have a better understanding of its mechanics now and the info could definitely be useful on future projects.
 
I cannot seem to access cboBox from an application that references to my UserControl if I don't declare it public within my UserControl project. So "LocationSelector.cboBox" is not something that can be accessed if I don't specifically create a public that ties to it - am I missing something?
That was what I said. Assembly is a compiled application or library.

As the developer everything is your choice, but in case you misinterpreted this; "Designer" is visual part in IDE, as in "View Designer" :)
 
Back
Top