I have a Silverlight UserControl which uses the ContentPropertyAttribute to exposes the Children property of one of it's child panels. This allows me to add child controls to the panel on my page:
<local:MyUserControl>
<TextBox Name="tbTest" />
</local:MyUserControl>
This works, apart from the 'tbTest' field of the page being present, but not initialised. On closer inspection, the InitializeComponent method does try to locate the TextBox (with FindName), but fails to do so (returning null).
After some investigation, I've found that namescopes are the problem - the UserControl has it's own namescope, thus it's children can't be located with the page's FindName but can with the UserControl's FindName method.
How can I alter my UserControl so that the child controls are locatable by the InitializeComponent method? The standard Panels (StackPanel, Grid, etc.) don't seem to have any problem doing so, so there must be a solution?
Thanks
It may be difficult to do at this point but the best course of action would probably be to derive your control from ItemsControl instead of UserControl. Then you wouldn't have the problem with name scopes.
I suppose as a workaround you could do a dive into the control with VisualTreeHelper to manually set the tbTest field.
Related
I've been experimenting with WPF, Xaml, MVVM, and DependencyInjection lately. Consequently, I am creating a UI using MVVM principles. A certain portion of the UI is designed to act like a wizard wherein not all of the available options are presented to the user at the same time. Each section of options is its own View (sub-View) with a single View (Parent View) hosting these sub-Views in a ContentControl. The user sets certain options and uses buttons to move from one section to the other.
View Navigation
To switch between these views I'm using a DataTemplateSelector with each sub-View defined as a DataTemplate in my Xaml resources.
Content Control in the Main View:
<ContentControl Content="{Binding ElementName=ParentViewControl, Path=ViewState, Mode=TwoWay}"
ContentTemplateSelector="{StaticResource MyTemplateSelector}" />
Example sub-View Data Template:
<DataTemplate x:Key="SubViewATemplate">
<local:SubViewAView x:Name="SVAView" DataContext="{Binding ElementName=ParentViewControl, Path=DataContext}" ViewState="{Binding ElementName=ParentViewControl, Path=ViewState, Mode=TwoWay }" />
</DataTemplate>
On the Parent View and each sub-View I've created a Dependency Property called ViewState (an enum). These bind to each other through the DataTemplates. In each View's code-behind I update this ViewState Property based off user input and it propagates up to the Parent View which in turn triggers the DataTemplateSelector. So far, so good. The navigation works beautifully.
ViewModel Info
The Parent View has a ViewModel which implements INotifyPropertyChanged as its DataContext. I'm attempting to use this single ViewModel to bind Properties to the Parent View and the sub-Views. The problem is the DataContext binding in the DataTemplate snippet above does not work. (Which is odd to me since the ViewState binding does.) After various attempts to get this to work, the DataContext on a sub-View is either null or the ViewState control variable.
I am currently using the UnityContainer as my dependency injector.
Various Attempts
Here are the various other things I've tried that have all failed:
1) Registered the ViewModel as a singleton in the UnityContainer thereby using Constructor Injection on the sub-Views to set the DataContext. (Does not work because there must be a Parameter-less Constructor for the DataTemplate resource.)
2) Registered the ViewModel as a singleton in the UnityContainer and then using Property Injection on the sub-Views to set the DataContext. (Does not work. I think this is due to the UnityContainer not working when an object is instantiated in Xaml.)
3) Creating sub-ViewModels for each sub-View that needs a ViewModel to display properties that would have existed on the Parent ViewModel. I've used this before to get around the Xaml instantiation problem with the UnityContainer. I then replace the DataContext binding in the DataTemplate with the associated sub-ViewModel. (Does not work because for some reason the DataContext of my Parent View is getting set to the ViewState variable instead of remaining my ViewModel which I've set in the view's Constructor. This in turn means the sub-ViewModel property on my Parent ViewModel can't be found to bind to the DataContext of the sub-View.) Are the Content and DataContext of UserControls the same thing? Does setting one affect the other?
4) Moving the ViewState dependency property from the View to the ViewModel and then setting the ContentControl's Content to bind to the ViewModel. This violates MVVM principles but by this time I was trying anything to get this to work. (It doesn't work because when ViewState is changed in the code-behind of the view the ViewModel does not trigger as changed.) I haven't gone any further with this one because I didn't want to go deeper violating MVVM.
Conclusion
I've found most of these attempted solutions on this site over the last couple days. I haven't had any formal training in WPF, Xaml, and MVVM so I suspect I'm missing something obvious, or am attempting to do something that isn't possible. I'm going to keep attempting variations on the above and researching until I find something that works, but I thought I would tap into the collective knowledge here to help me find a solution.
What I'd prefer is to have the group of Views use the single ViewModel as their DataContext so I can bind properties to their controls. And have the Views' navigation be controlled by a DataTemplateSelector. Is there a way to do this that I'm not seeing?
Thank you for your time!
I have had similar issues before, I have had good luck using a RelativeSource binding. Maybe try something like this:
DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ContentControl},Path=DataContext}"
Just a thought.
I have a rather interesting case with ComboBox control - CustomComboBox;
In the style of this ComboBox, Popup contains one custom control that requests a DataContext;
<ctrl:CustomGrid DataContext="{TemplateBinding DataContext}" GridName="{Binding Preferences.CurrentGridName}"/>
The idea:
to use this control several times on one page
to use it in a masterpage container
the masterpage control needs to have different DataContexts regarding the Page it is on
The logic:
In the overriden OnApplyTemplate I am getting the grid and connecting few eventhandlers
The problem:
The masterpage control is triggering OnApplyTemplate only once
The first appearance of the CustomComboBox is as expected.
However, every next apearance is with same DataContext, even when changing the datacontext of the CustomComboBox
These changes don't reach to change my CustomGrid DataContext
I am sure that something on the bindings or the presentation logic is bad...
Please throw some thoughts on, I would appreciate a hint here
Thanks
OnApplyTemplate is called when a ControlTemplate is applied to the control that overrides the method (neither its parent, nor children). If OnApplyTemplate is entered once, the overriding control must also be created once. I mean you simply have a single masterpage instance. This shouldn't be unexpected.
Speaking about Popups and DataContext, there often are issues with bindings from a Popup to outside it. So, I would rather write some code-behind to deliver correct context to Popups, instead of relying on Bindings. There sure is a problem of DataContextChanged event absence prior to SL5. To workaround this one, you should define your custom DependencyProperty on your CustomComboBox, bind it to the CustomComboBox's context and assign its value to the Popup in the PropertyChangedCallback.
I have a few usercontrols loaded into a tabcontrol via MVVM in WPF.
Within the XAML for the usercontrol I am setting focus to a textbox using the FocusManager, however this appears to only work when the first instance of the usercontrol is created.
Just to test I added a loaded event handler to the usercontrol - this is only called on the first instance.
I'm using data templates for the user controls as follows:
<DataTemplate DataType="{x:Type local:UserTypeViewModel}">
<local:UserTypeView />
</DataTemplate>
The textbox is focused as follows:
FocusManager.FocusedElement="{Binding ElementName=txtName}"
Additionally I'm using a global event handler (for the textbox GotFocus event) which selects all the text using a dispatcher.
If anyone has any tips on how to achieve focus with every usercontrol I'd be very grateful.
Thanks, Ben.
Remember that a visual element can only receive focus, if:
It is visible (in a TabControl only one tabitem can be visible at a time
IsFocusable must be set to true (is default false for UserControls)
It has finished loading (as you write - do it in the Loaded event))
I think the first reason is why it only works for the first element.
As for how to achieve it for all controls - you can use a style with an EventSetter for the Loaded event. You need to make a style per type of control though to avoid having to set it for each control.
I have a user control that allows items to be added to it by exposing a Grid's Children property. Any control I add shows up fine but when I try to bind a property on the added item to a control in the main window nothing happens (example):
<TextBox Name="txtTest" Text="Success!" />
<mycontrols:CustomUserControl.ExposedGridChildren>
<TextBox Text="{Binding ElementName=txtTest, Path=Text, FallbackValue=fail}"/>
</mycontrols:CustomUserControl.ExposedGridChildren>
This example always results in the TextBox's text showing "fail". Here is how I'm exposing the children in the user control:
public UIElementCollection ExposedGridChildren
{
get { return grdContainer.Children; }
}
Any thoughts? Is it a scope issue? I know I can't name the elements I add to the children because of scope errors. Thanks, Brian.
This might answer your question:
How do you bind a grid's children to a list?
Or as Dr. WPF puts it: (http://drwpf.com/blog/2007/10/15/itemscontrol-a-is-for-abundance/)
Is a Panel an ItemsControl?
No. The logical children of a panel
are UIElements, whereas the logical
children of an ItemsControl (its
Items) can be any CLR objects.
Sidebar: So what is a panel? The main
role of a panel is to provide layout
support for its children. Although a
panel does maintain a collection of
child elements, it is not technically
a WPF “control”… That is, it does not
derive from the Control base class and
it does not support the WPF notion of
templating. Instead, it is a single
element with a single purpose… namely,
to size and position (a.k.a., measure
and arrange) its children.
I was apparently going about this thing all wrong. What I needed to do was create a look-less control and template it to have one (or more) contentpresenter(s).
Could anyone explain how HierarchicalDataTemplate works
What Controls supports HierarchicalDataTemplate?
What does a control need to support HierarchicalDataTemplate?
UPDATE
What causes the TreeView to render
the parent and child nodes when the
same HierarchicalDataTemplate in a
HeaderedItemsControl only causes the
parent to be rendered?
What Controls supports HierarchicalDataTemplate?
All controls that inherit HeaderedItemsControl, such as TreeViewItem or MenuItem
What does a control need to support HierarchicalDataTemplate?
Inheriting from HeaderedItemsControl should be enough
Such control needs to be of type HeaderedItemsControl or derived from it. The current framework controls that do are MenuItem, ToolBar and TreeViewItem.
The HeaderedItemsControl overrides the PrepareContainerForItemOverride method and somewhere along that call path checks for HierarchicalDataTemplate.