Getting WPF Templated items from Control and Data templates - wpf

How do I get a named control from a control that is bound to a control template or data template?
I have tried FindName it does not work.
I prefer not to use the VisualTreeHelper as you have to traverse through each parent child item individually.

It depends when you do it. If you do it in the constructor it wont work as the element only exists after the template has been applied.
This is the standard way to do it if you create the control:
public override void OnApplyTemplate() {
//i call the base first
base.OnApplyTemplate();
//then go looking for the newly created elements
TextBox textBox = this.Template.FindName("PART_TextBox", this) as TextBox;
}

Related

Master-detail: How to fetch a control from a template inside the "detail" ContentControl?

I have a ListView (on the 'master' side) whose selection drives a ContentControl's Content property (on the 'detail' side). The ContentControl's visual tree comes from either of two DataTemplate resources that use DataType to choose which detail view to render based on what is selected in the ListView.
That part works fine.
The part I'm struggling with is that there is a particular control inside (one of) the templates that I need to obtain a reference to whenever it changes (e.g. the template selected changes or the ListView selection changes such that the instance of the control is recreated.)
In my ListView.SelectionChanged event handler, I find the ContentControl has not yet been updated with its new visual tree, so initially it's empty on the first selection, and for subsequent selections its visual tree matches the old selection instead of the new one.
I've tried delaying my code by scheduling on the Dispatcher with a priority as low as DispatcherPriority.Loaded, which works for the first selection but on subsequent selections my code still runs before the visual tree is updated.
Is there a better event I should be hooking to run whenever the ContentControl's visual tree is changed to reflect a changed data-bound value to its Content property?
Extra info: the reason I need to reach into the expanded DataTemplate is that I need to effectively set my view model's IList SelectedItems property to a DataGrid control's SelectedItems property. Since DataGrid.SelectedItems is not a dependency property, I have to do this manually in code.
The fix required a combination of techniques. For the first selection that populates the visual tree, I needed to handle ContentControl.OnApplyTemplate() which is only a virtual method rather than an event. I derived from it and exposed it as an event:
public class ContentControlWithEvents : ContentControl
{
public event EventHandler? TemplateApplied;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.TemplateApplied?.Invoke(this, EventArgs.Empty);
}
}
In the XAML I used the above class rather than ContentControl:
<local:ContentControlWithEvents
Content="{Binding SelectedAccount}"
x:Name="BankingSelectedAccountPresenter"
TemplateApplied="BankingSelectedAccountPresenter_TemplateApplied" />
Then I handle the event like this:
void BankingSelectedAccountPresenter_TemplateApplied(object sender, EventArgs e) => this.UpdateSelectedTransactions();
private void UpdateSelectedTransactions()
{
if (this.MyListView.SelectedItem?.GetType() is Type type)
{
DataTemplateKey key = new(type);
var accountTemplate = (DataTemplate?)this.FindResource(key);
Assumes.NotNull(accountTemplate);
if (VisualTreeHelper.GetChildrenCount(this.BankingSelectedAccountPresenter) > 0)
{
ContentPresenter? presenter = VisualTreeHelper.GetChild(this.BankingSelectedAccountPresenter, 0) as ContentPresenter;
Assumes.NotNull(presenter);
presenter.ApplyTemplate();
var transactionDataGrid = (DataGrid?)accountTemplate.FindName("TransactionDataGrid", presenter);
this.ViewModel.Document.SelectedTransactions = transactionDataGrid?.SelectedItems;
}
}
}
Note the GetChildrenCount check that avoids an exception thrown from GetChild later if there are no children yet. We'll need that for later.
The TemplateApplied event is raised only once -- when the ContentControl is first given its ContentPresenter child. We still the UpdateSelectedTransactions method to run when the ListView in the 'master' part of the view changes selection:
void BankingPanelAccountList_SelectionChanged(object sender, SelectionChangedEventArgs e) => this.UpdateSelectedTransactions();
On initial startup, SelectionChanged is raised first, and we skip this one with the GetChildrenCount check. Then TemplateApplied is raised and we use the current selection to find the right template and search for the control we need. Later when the selection changes, the first event is raised again and re-triggers our logic.
The last trick is we must call ContentPresenter.ApplyTemplate() to force the template selection to be updated before we search for the child control. Without that, this code may still run before the template is updated based on the type of item selected in the ListView.

How do I make user controls for both ListView and ListViewItem work with each other?

I have all the styling, triggers, etc. down for ListView and ListViewItem, and I want to turn them into user controls. How do I make sure that these two "match up" with each other, so that MyListView accepts MyListViewItems as content? Also, considering that I must end the ListView tag by the end of the user control XAML file, I am not sure how I would add items to it.
If you want them to be reusable with different data sets, especially through binding, you should stay away from UserControls and just make custom controls derived from the original types. In that case you create a standalone MyListView.cs and MyListViewItem.cs and all of the XAML for the controls goes into default Styles (usually also containing a ControlTemplate) in Themes/Generic.xaml. You can see an example of this setup by just adding a WPF Custom Control to your WPF project from Add New Item.
Once you've created the .cs files for your custom controls you just need to override a few methods from the base ItemsControl to use MyListViewItem as the item container control. The ListView would end up like this:
public class MyListView : ListView
{
static MyListView()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyListView), new FrameworkPropertyMetadata(typeof(MyListView)));
}
protected override DependencyObject GetContainerForItemOverride()
{
return new MyListViewItem();
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is MyListViewItem;
}
}
You can now use your custom MyListView exactly as you would a normal ListView, including binding to ItemsSource.
Inheritance should take care of that for you. In other words, if you have two user controls, the first one with a basic element of ListView (not UserControl) and the other of ListViewItem (again, not UserControl), and you make sure they extend ListView and ListViewItem respectively in the .cs code, the following should work equally:
ListView lv = new ListView();
lv.Items.Add(new ListViewItem());
or
MyListView mlv = new MyListView();
mlv.Items.Add(new myListViewItem()); //If your myListView extends ListView, and myListViewItem extends ListViewItem in your user control files, of course
In case you are looking for a XAML solution, you should import your namespace at the top
xmlns:myControls="WhateverYourNamespaceAndAssemblyAre"
and on you page/window/whatever
<myControls:myListView>
<myControls:myListViewItem/>
<myControls:myListViewItem/>
</myControls:myListView>

How can I get the the selected visual content of a TabControl?

My custom TabControl uses a ContentTemplate in order to show the selected content. From within the TabControl, how can I get the actual Visual Content? SelectedTabItem.Content is my viewmodel.
The selected content presenter is a templated part of TabControl, so we can do this in our subclass:
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_selectedContentHost = Template.FindName("PART_SelectedContentHost", this) as ContentPresenter;
}
If you look at the TabControl Class and TabItem Class pages at MSDN, you will see that they both extend the System.Windows.Media.Visual Class. Therefore the whole object is the Visual object and can be used as a parameter in any method that takes a Visual object.

WPF element that dynamically creates (encapsulated) children at runtime

I want to create a WPF element that, at runtime, is in full control of its child elements -- adding and removing child UI when its properties change. Something a bit like what ItemsControl does when you modify its ItemsSource property, though in my case there'll only be one child.
This will be a view container for MVVM -- when you give it a Model or a ViewModel, it will magically create the correct View and wire everything up. There's no need for my view container to be templatable (since it creates user-defined views, which are UserControls and have their own templates), and I'd prefer that it encapsulate as much as possible. I could probably do this easily by descending from something like Grid, and adding child controls when my own properties change; but Grid publicly exposes its collection of child elements and lets anyone add and remove stuff.
Which WPF class should I descend from for maximum encapsulation, and how do I add child elements to it at runtime?
Based on my understanding of the docs, I tried using FrameworkElement and AddVisualChild, just to see if I could create child controls at runtime. I'm not clear on whether the AddLogicalChild is necessary, but I put it in just in case:
public class ViewContainer : FrameworkElement {
private TextBlock _child;
public ViewContainer() {
_child = new TextBlock { Text = "ViewContainer" };
AddLogicalChild(_child);
AddVisualChild(_child);
InvalidateMeasure();
}
public object Content { get; set; }
protected override Size ArrangeOverride(Size finalSize) {
_child.Arrange(new Rect(finalSize));
return finalSize;
}
protected override Size MeasureOverride(Size availableSize) {
_child.Measure(availableSize);
return _child.DesiredSize;
}
}
When I put a ViewContainer into a Window, and run this, I expect to see a TextBlock saying "ViewContainer". But instead, I just see a blank window. So obviously I'm missing something.
How can I fix the above code so that the "child" control does appear at runtime, but isn't exposed for others to mess with (any more than can be avoided)?
To answer your specific question, you'll also need to override GetVisualChild and VisualChildrenCount properties to enable your child element to be displayed.
Have you thought about taking advantage of WPF's support for implicit DataTemplates?
The way I have handled a requirement similar to yours is by using a ContentControl. I bind the Content property to my ViewModel. I then make sure that in Resource Dictionaries referenced somewhere in the tree above the ContentControl I have DataTemplates defined for all the types of ViewModels that might be assigned to the Content Property.
This way WPF takes care of wiring up the correct view for my ViewModel.

How can I determine the type of the items that get added to a TabControl?

I've created a CloseableTabItem control that derives from TabItem.
Now I'd like to specify that a given TabControl should add new items using CloseableTabItem instead of TabItem.
Is this possible? How?
public class CloseableItemsTabControl : TabControl
{
protected override DependencyObject GetContainerForItemOverride()
{
return new CloseableTabItem();
}
}
You'll probably need to make your own ClosableTabControl that extends TabControl in order to override the base functionality.
However, you can also probably just add your tabs manually, feeding it your ClosableTabItems instead of regular TabItems. It would be safe to assume this is possible since most collection-based controls are able to be programatically populated this way.

Resources