Exposing a sub-control in a UserControl for use in XAML - wpf

I have a UserControl that contains a TreeView. I want the user to be able to set the properties of the inner TreeView control via XAML and I'm not sure how to do that.
I've tried creating a public property on the UserControl to the TreeView, but that only allows me to set a SelectedItemChanged trigger.
I'd like to do something like:
<ExampleUserControl>
<ExampleUserControl.TreeView.ItemTemplate>
...
</ExampleUserControl.TreeView.ItemTemplate>
</ExampleUserControl>
Or:
<ExampleUserControl TreeView.ItemsSource="{Binding Foo}" />
I would prefer not to create properties in the UserControl for each TreeView property, and I don't want to force the user to define the control in C#.

As for passing multiple properties to the child control in your user control, you can always expose a Style property.
ie ChildStyle
For the ItemsSource unless you use [Josh Smith's Element Spy / Data Context Spy / Freezable][1] trick, you will have a disconnect on DataContexts.
So either you employ those tricks or simply have 2 properties.
1) the ItemsSource
2) the ChildStyle
The xaml ends up...
<ChildTreeAnswer:MyControl ItemsSource="{Binding Items}">
<ChildTreeAnswer:MyControl.ChildStyle>
<Style>
<Setter Property="ItemsControl.ItemTemplate">
<Setter.Value>
<DataTemplate>
<Border BorderBrush="Black"
BorderThickness="1"
Margin="5">
<TextBlock Text="{Binding }" />
</Border>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</ChildTreeAnswer:MyControl.ChildStyle>
</ChildTreeAnswer:MyControl>
Then in your user control do... (I used a listbox for simplicity sake)
<ListBox ItemsSource="{Binding ItemsSource}"
Style="{Binding ChildStyle}" />

Related

How do I properly set this button's binding to the parent ListView ItemsControl?

I have a custom button set up inside a ListView ItemTemplate. The Listview's ItemSource is bound to a collection of items, pretty standard. I have a few labels in the listview as well, and everything works fine except the button.
Binding the button to one of the properties won't work at all using {Binding buttonName} but it will sort of work if I use {Binding Items/buttonName, ElementName=listView} - the only problem is, when I do it this way, every single button in that listView will have the exact same buttonName.
Now the issue stems from my custom button's DataContext being set to Self; unfortunately, it has to be set to Self because the custom style I'm using needs this. If I try to change the button to a UserControl instead (with the button as a child, and the DataContext set on that), then I can't use the Command property of the button for some reason.
Here's a simplified version of my ListView making use of the custom button:
<ListView x:Name="listView" ItemsSource="{Binding MyPeopleData}">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Label Content="{Binding PersonName}"/>
<ct:RevealButton Content="{Binding Items/recommendation, ElementName=listView}"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
As I said above, this will make every item in the listview use the same recommendation property rather than using it's own one.
If I try to use
<ct:RevealButton Content="{Binding recommendation}"/>
It just won't work, which makes sense given the DataContext of the custom button, which is below:
<Button x:Class="RevealButton" Width="{Binding Width}" Height="{Binding Height}" Background="{Binding ButtonBackground}" DataContext="{Binding RelativeSource={RelativeSource Self}}" Style="{DynamicResource ButtonRevealStyleC}" mc:Ignorable="d">
<Button.ContentTemplate>
<DataTemplate>
<ContentPresenter Content="{TemplateBinding Content}" />
</DataTemplate>
</Button.ContentTemplate>
</Button>
So this ended up being an XY Problem. Because the modified style I was using had a poorly bound property, it was failing when the parent control didn't have a DataContext set to self.
This is what it was:
<SolidColorBrush Opacity="0.8" Color="{Binding ButtonBackground.Color}" />
And ButtonBackground was a dependency property exposed by my custom Button, so binding the style in this way meant it only worked if the Button's context was itself. Changing it to this:
<SolidColorBrush Opacity="0.8" Color="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=ButtonBackground.Color}" />
Fixed the DataContext dependency, which in turn fixes the heirarchy of problems above.

Map control within Listbox

I am trying to display multiple maps within an Listbox.
<Grid Name="MainGrid">
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Visible" >
<WrapPanel Name="wrap" >
<ListBox ItemsSource="{Binding MyList}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Border Margin="5" MinWidth="500" MinHeight="400" BorderThickness="2" BorderBrush="Black"
Width="200"
Height="200" >
<esri:MapView MouseDown="MapView_MouseDown" MouseUp="MapView_MouseUp" >
<esri:Map >
<esri:ArcGISTiledMapServiceLayer ID="BaseMap" ServiceUri="http://services.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer"/>
</esri:Map>
</esri:MapView>
</Border>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</WrapPanel>
</ScrollViewer>
</Grid>
The map does not get displayed. Only the logo "esri" gets displayed. But if I remove the listbox, it works fine. What could be the issue?
I am very sure that there are items in my listbox otherwise "esri" would not have appeared.
I have tried Itemscontrol as well but its the same result.
In WPF, we don't put UI elements into collections, we put data objects into collections. Each data item should contain whichever properties are needed to data bind to the UI control that you actually want to display. So your MyList collection should contain a collection of data objects.
Once you have created a custom class to contain these required properties, you will then need to declare a DataTemplate that defines which UI element(s) to display. (See the Data Templating Overview page on MSDN for further information on this).
<ControlTemplate x:Key="YourCustomControlTemplate">
<!-- Define your UI content here -->
</ControlTemplate>
Once you have declared your custom ControlTemplate for your custom data class, you should then put it into a Style for your data object:
<Style TargetType="{x:Type YourXamlPrefix:YourCustomDataClass}">
<Setter Property="Template" Value="{StaticResource YourCustomControlTemplate}" />
</Style>
Note that I have omitted the x:Key directive on this Style... you can add one, but without one (and as long as this Style has been declared in scope of the UI), the Style will be automatically applied to your data objects in the collection.

Get context in which a view is rendered?

In my WPF application I have a viewmodel class called CompanyViewModel.
Sometimes, an instance of this class is set as the DataContext of my main window, which is defined like this:
<window x:Class= ..... >
<Grid>
<ContentControl Content="{Binding }"></ContentControl>
</Grid>
</Window>
In this case I want a view to be used that displays all the properties of the viewmodel.
Other times, a ListView control has its itemsource set as a collection containing instances of CompanyViewModel. Here, I want a view to be used that renders only some important properties.
I have this in the resource dictionary of MainWindow.xaml:
<DataTemplate DataType="{x:Type vm:CompanyViewModel}">
<vw:CompanyView></vw:CompanyView>
</DataTemplate>
Is it possible to select a view for the viewmodel based on the context where the viewmodel is bound? For instance, to use CompanyView when displayed in the ContentControl of a window or when in a TabControl, and to use CompanyViewSmall where displayed in a ListView?
The DataTemplate to use is first looked for locally, and then looked for further up the Visual Tree hierarchy if it's not found.
Because of this, you can specify the DataTemplate to use further down the hierarchy to use something different than normal.
For example, the following will use the CompanyView anywhere the CompanyViewModel is in the visual tree, except in the specific ListView where the DataTemplate is specified as the smaller view.
<Window.Resources>
<DataTemplate DataType="{x:Type vm:CompanyViewModel}">
<vw:CompanyView />
</DataTemplate>
</Window.Resources>
<ListView>
<ListView.Resources>
<DataTemplate DataType="{x:Type vm:CompanyViewModel}">
<vw:CompanyViewSmall />
</DataTemplate>
</ListView.Resources>
</ListView>
You could also use an implicit style for the ListView telling it to use the smaller template in the .Resources, however this will apply the smaller view to any ListView, not just specific ones, and if you ever apply another style to a ListView you'll have to remember to inherit the default style to keep the smaller DataTemplate.
<Style TargetType="{x:Type ListView}">
<Style.Resources>
<DataTemplate DataType="{x:Type vm:CompanyViewModel}">
<vw:CompanyViewSmall />
</DataTemplate>
</Style.Resources>
</Style>

Enable a TabItem via Binding

I want to use MVVM in an application where the different pages are TabItems.
For this I use an observable collection of my view models (Items) and bind it to the tabcontrols ItemSource.
For each view model, I created an individual data template to render the correct view like this:
<DataTemplate DataType="{x:Type baseVm:AViewModel}">
<baseVw:AView />
</DataTemplate>
To display the correct name in the tab's header I created another data template to be applied to each of the tab control's elements:
<DataTemplate x:Key="ViewModelTabTemplate">
<DockPanel>
<ContentPresenter Content="{Binding Path=Name}"/>
</DockPanel>
</DataTemplate>
The tab control looks like this:
<TabControl x:Name="myTabControl"
ItemsSource="{Binding Items}"
ItemTemplate="{DynamicResource ViewModelTabTemplate}">
</TabControl>
What I want to do now is to enable/disable tabs from within the view model that contains the collection. The view model's base class contains a dependency property IsEnabled and I would like to bind this to the views. If I do this directly in the view like this:
IsEnabled="{Binding IsEnabled, FallbackValue=true}"
the tab page's content gets disabled when I turn the IsEnabled property to false. But what I really want is to also disable the tabpage's tab and not just the content.
Thanks for any help!
Maybe you could try something like this -
<TabControl>
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}">
<Setter Property="IsEnabled" Value="{Binding IsEnabled}"/>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>

How to bind to Tabcontrol.Items

I have a WPF application that I'm trying to dynamically add items to a tabcontrol. I have a list of menu items that should be databound to the tabcontrol's items. The only problem is that TabControl.Items does not notify others that items have been added. I've tested this by binding instead to TabControl.Items.Count and get calls to the converter (but the value passed in is the count and not something useful). Here's the relevent code that doesn't get databound properly because Items doesn't call out updates:
<MenuItem ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TabControl}}, Path=Items, Converter={StaticResource TabControlItemConverter}}">
This MenuItem XAML is inside a ControlTemplate for a TabControl. With static items, i.e., items that are already defined in a TabControl, this code works perfectly. But I have a TabControl that gets items added at runtime and can't seem to update this binding. Has anyone added some sort of attached property to a TabControl that can bind to the Items collection?
Edit for background info
The TabControl that has items added to it is a region (this is a Prism application). Here is the relevent XAML
<TabControl cal:RegionManager.RegionName="{x:Static local:LocalRegionNames.SelectedItemRegion}" >
<TabControl.Resources>
<Style TargetType="TabItem" BasedOn="{StaticResource TabItemStyle}">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Style="{StaticResource tabItemImage}" Height="20" />
<TextBlock Text="{Binding Content.DataContext.TabHeader, RelativeSource={RelativeSource AncestorType=TabItem}}" VerticalAlignment="Center" />
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.Resources>
</TabControl>
The relevent code for adding a view to the region is here:
ProjectDetailView view = new ProjectDetailView();
ProjectDetailViewModel viewModel = new ProjectDetailViewModel();
viewModel.CurrentProject = project;
view.DataContext = viewModel;
IRegionManager retManager = RegionManager.Regions[LocalRegionNames.SelectedItemRegion].Add(view, null, true);
RegionManager.Regions[LocalRegionNames.SelectedItemRegion].Activate(view);
All this works fine...views get added, the tab control adds items, and views appear. But the Items property on the tabcontrol never broadcasts the changes to its collection.
You do the same thing for TabControls, you bind the ItemsSource, the only thing you need to take into account is that the source collection should implement INotifyCollectionChanged if you want it updated if items are added. ObservableCollection<T> already implements the interface and is often used as source for such bindings.

Resources