MVVM Hooking up nested child views to child view model - c#

I'm trying to institute nested ViewModels in my already working application which uses nested views. Here's an example of what I want to do:
MainWindow View:
<Window x:Name="FCTWindow" x:Class="CatalogInterface.MainWindow"
xmlns:local="clr-namespace:CatalogInterface"
xmlns:vm="clr-namespace:CatalogInterface.ViewModels"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="532">
<Window.Resources>
<vm:MainWindowViewModel x:Key="ViewModel" />
</Window.Resources>
<Grid DataContext="{Binding Path=ViewModel.DirFilesListBoxViewModel}" x:Name="BodyGridLeft" Grid.Row="0" Grid.Column="0">
<local:ctlDirFilesListBox>
<!--
Need to access the `ItemsSource="{Binding }"` and
`SelectedItem="{Binding Path=}"` of the ListBox in
`ctlDirFilesListBox` view -->
</local:ctlDirFilesListBox>
</Window>
Child View:
<UserControl x:Class="CatalogInterface.ctlDirFilesListBox"
xmlns:local="clr-namespace:CatalogInterface"
xmlns:vm="clr-namespace:CatalogInterface.ViewModels"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid x:Name="MainControlGrid">
<ListBox SelectionChanged="ListBoxItem_SelectionChanged"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="#FFFFFF"
Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="3" BorderThickness="0">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}" BasedOn="{StaticResource {x:Type ListBoxItem}}">
<EventSetter Event="MouseDoubleClick" Handler="ListBoxItem_MouseDoubleClick"/>
<EventSetter Event="KeyDown" Handler="ListBoxItem_KeyDown"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</Grid>
</UserControl>
MainWindowViewModel
using System;
using System.Text;
namespace CatalogInterface.ViewModels
{
class MainWindowViewModel
{
public DirFilesViewModel DirFilesViewModel { get; set; }
public MainWindowViewModel()
{
DirFilesViewModel = new DirFilesViewModel();
}
}
}
So, I need to hook ListBox.SelectedItem and ListBox.ItemSource to bind with properties in MainWindowViewModel.DirFilesViewModel. The catch is I have to do the binding in MainWindow View not ctlDirListBox view.
How do i access elements inside my child view? I think that's my biggest barrier. I think all my data context is right, I just can't wrangle the child view elements.

I'm assuming that DirFilesViewModel is the viewmodel for that usercontrol. If that's not the case, let me know what the real situation is and we'll get it sorted out.
This is a very simple case. #JamieMarshall If the XAML you provided is all there is to your UserControl, maybe it shouldn't be a usercontrol at all. You could just write a DataTemplate with that XAML in it and use that, or you could write a Style for ListBox. If you need the events, then a UserControl makes sense, but you may not actually need the events.
But it could just be a minimal example to understand how UserControls are used, and for that purpose it's well suited.
You can assign an instance of your main viewmodel to the main window's DataContext in the window's constructor,
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
or in the XAML as
<Window.DataContext>
<vm:MainWindowViewModel />
<Window.DataContext>
Neither is particularly preferable, just don't do either one in a UserControl. Your main window is pretty much the only time a view (a window is a view, properly considered) should create its own viewmodel.
Making it a resource doesn't add anything. Your binding to Grid.DataContext is a bad idea -- it's rare that you ever bind anybody's DataContext to anything; this is related to what Will was talking about in your other question -- but even if it were a good idea, this is what the binding would look like:
<Grid
DataContext="{Binding Source={StaticResource ViewModel}}"
>
But don't do that!
One thing you can do to display that usercontrol with the correct data is create "implicit datatemplates" for your your viewmodels that'll be displayed in parents like this one.
For example:
App.xaml
<!-- No x:Key, just DataType: It'll be implicitly used for that type. -->
<DataTemplate DataType="{x:Type vm:DirFilesViewModel>
<local:ctlDirFilesListBox />
</DataTemplate>
Then in MainWindow.xaml:
<UserControl
Grid.Row="0"
Grid.Column="0"
Content="{Binding DirFilesViewModel}"
/>
XAML will go to the window's DataContext for a property named DirFilesViewModel. What it finds there is an object that's an instance of the class also named DirFilesViewModel. It knows it has a DataTemplate for that class, so it uses that datatemplate.
This is amazingly powerful: Imagine you have an ObservableCollection<ViewModelBase> with thirty instances of ten different kinds of viewmodels with different views, and the user selects one or another. The selected viewmodel is in a mainviewmodel property named SelectedChildVM. Here's the XAML to display SelectedChildVM with the correct view:
<ContentControl Content="{Binding SelectedChildVM}" />
That's it.
Moving along:
<!--
Need to access the `ItemsSource="{Binding }"` and
`SelectedItem="{Binding Path=}"` of the ListBox in
`ctlDirFilesListBox` view -->
No you don't! That's the last thing you want to do! Some UserControls have properties of their own, instead of a viewmodel. With those, you bind the properties in the parent like any control.
This is a different use case of UserControls: It's "parameterized" by inheriting a viewmodel as its DataContext. The information you give it is the viewmodel.
The controls in the UserControl should have their own bindings, where they get that stuff from properties of the UserControl's viewmodel.
Let's assume the usercontrol's viewmodel (I'm guessing that's what DirFilesViewModel is) has a Files property (ObservableCollection<SomeFileClass>) and a SelectedFile class (SomeFileClass). You likely don't need ListBoxItem_SelectionChanged.
<UserControl x:Class="CatalogInterface.ctlDirFilesListBox"
xmlns:local="clr-namespace:CatalogInterface"
xmlns:vm="clr-namespace:CatalogInterface.ViewModels"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid x:Name="MainControlGrid">
<ListBox
ItemsSource="{Binding Files}"
SelectedItem="{Binding SelectedFile}"
SelectionChanged="ListBoxItem_SelectionChanged"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="#FFFFFF"
Grid.Row="2"
Grid.Column="1"
Grid.ColumnSpan="3"
BorderThickness="0"
>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}" BasedOn="{StaticResource {x:Type ListBoxItem}}">
<EventSetter Event="MouseDoubleClick" Handler="ListBoxItem_MouseDoubleClick"/>
<EventSetter Event="KeyDown" Handler="ListBoxItem_KeyDown"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</Grid>
</UserControl>

How do i access elements inside my child view?
You could add two dependency properties (for example named ItemsSource and SelectedItem) to the code-behind class of your ctlDirFilesListBox control and bind to these in the parent window:
<local:ctlDirFilesListBox ItemsSource="{Binding Property}" SelectedItem="{Binding Property}" />
You should also bind to these properties in the UserControl:
<ListBox SelectionChanged="ListBoxItem_SelectionChanged"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="#FFFFFF"
Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="3" BorderThickness="0"
ItemsSource="{Binding ItemsSource, RelativeSource={RelativeSource AncestorType=UserControl}}"
SelectedItem="{Binding SelectedItem, RelativeSource={RelativeSource AncestorType=UserControl}}">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}" BasedOn="{StaticResource {x:Type ListBoxItem}}">
<EventSetter Event="MouseDoubleClick" Handler="ListBoxItem_MouseDoubleClick"/>
<EventSetter Event="KeyDown" Handler="ListBoxItem_KeyDown"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
public class ctlDirFilesListBox : UserControl
{
//...
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(ctlDirFilesListBox));
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register("ItemsSource", typeof(object), typeof(ctlDirFilesListBox));
public object SelectedItem
{
get { return GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
}

Related

How to hide tab control when item source list is empty

I have a WPF .NET Core application with a TabControl bound to an ObservableCollection for the TabItems. I would like the TabControl to be hidden when the ObservableCollection becomes empty, and I would like to display another panel in its place. Then, when an item is added back to the ObservableCollection, I want the alternate panel hidden and the TabControl reshown. How would I accomplish this, hopefully in XAML with as little code-behind as possible? I know I can do it in code-behind.
Below is the key section of the app. I have hidden the TabControl, and included a Border control to represent the Panel that I will show when the TabControl is hidden.
<Window x:Class="TabTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:TabTest"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Border Name="emptyTabPanel" Grid.Row="1" BorderBrush="{DynamicResource {x:Static SystemColors.ControlDarkBrushKey}}"
BorderThickness="1,1,1,1" Margin="5,0,5,5" Visibility="Hidden">
</Border>
<TabControl Name="MainTabControl" Visibility="Visible">
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Header}" MinWidth="60"/>
<Button BorderThickness="0" Background="Transparent" Height="16" Width="15" Margin="15,2,0,0">
<Image Source="images/close.png" Height="8"/>
</Button>
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
</Grid>
</Window>
using System.Windows;
using System.Collections.ObjectModel;
namespace TabTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private ObservableCollection<TabEntry> tabEntries;
public MainWindow()
{
InitializeComponent();
tabEntries = new ObservableCollection<TabEntry>();
MainTabControl.ItemsSource = tabEntries;
for (int i = 1; i <= 5; i++)
{
tabEntries.Add(new TabEntry { Header = "tab " + i });
}
}
}
public class TabEntry
{
public string Header { get; set; }
}
}
All ItemsControls provide a HasItems property that you can use in a Trigger. In contrast to a DataTrigger on ItemsSource.Count this also works when ItemsSource is not set at all.
<TabControl ...>
<TabControl.Style>
<Style TargetType="TabControl">
<Style.Triggers>
<Trigger Property="HasItems" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</Trigger>
</Style.Triggers>
</Style>
</TabControl.Style>
...
</TabControl>
You can do it with a DataTriger in a Style. Note that you need to remove Visibility="Visible" or the Setter won't be able to change it.
<TabControl Name="MainTabControl" Background="Red">
<TabControl.Style>
<Style TargetType="TabControl">
<Style.Triggers>
<DataTrigger Binding="{Binding ItemsSource.Count, RelativeSource={RelativeSource Self}}" Value="0">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</TabControl.Style>
<TabControl.ItemTemplate>
<!-- and so on -->
You need a proper view model for this, rather than binding the tabs directly to the collection. That view model would include a HasItems property which you'll bind your TabControl visibility to, and an inverse property - say IsEmpty - which you'll bind the panel's visibility to.
Bind ObservableCollection's events to listen to changes in item count and raise PropertyChanged events for your view model appropriately.

How to access data from ViewModel in ItemsSource tag

I'm making TabControl that can change dynamically using ItemsSource tag.
I want to know the way to access ViewModel data in ItemsSource tag.
I searched through the Internet. but I couldn't find the answer.
CODE
public class ViewModel
{
// this will be used in ItemsSource
private ObservableCollection<ActiveButton> _allExecuteButtonInfos = new ObservableCollection<ActiveButton>();
public ObservableCollection<ActiveButton> AllExecuteButtonInfos
{
get { return _allExecuteButtonInfos; }
set {
_allExecuteButtonInfos = value;
OnPropertyChanged();
}
}
// I want to get this data in ItemsSource
private List<string> _boardNameList = new List<string>();
public string BoardNameList
{
get { return _boardNameList; }
set {
_boardNameList = value;
OnPropertyChanged();
}
}
}
XAML
<Grid>
<TabControl Background="#FF292929" ItemsSource="{Binding AllExecuteButtonInfos}">
<TabControl.ContentTemplate>
<DataTemplate>
<ScrollViewer Grid.Row="1" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" VerticalScrollBarVisibility="Auto">
<Grid VerticalAlignment="Stretch" Margin="0,0,0,0" >
<ComboBox Width="334" Margin="0,0,0,0" Grid.Column="1" Grid.Row="1" Height="22" VerticalAlignment="Top"
<!-- I want to get data from ViewModel not in ItemsSource(AllExecuteButtonInfos) -->
<!-- eg) VM:BoardNameList, ViewModel.BoardNameList etc -->
ItemsSource="{Binding BoardNameList, Mode=TwoWay , UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding SelectedBoard, Mode=TwoWay}"/>
</Grid>
</ScrollViewer>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
I hope I can find the answer.
Thank you.
You could bind to the DataContext, i.e. the view model, of the parent TabControl using a RelativeSource:
<ComboBox ...
ItemsSource="{Binding DataContext.BoardNameList, RelativeSource={RelativeSource AncestorType=TabControl}}" />
Note that it's pointless to set the Mode of an ItemsSource binding to TwoWay since the control never sets the property. It's also meaningless to set the UpdateSourceTrigger to PropertyChanged in this case for the same reason.
I am not sure where you've defined the data context but I suppose that it's somewhere above the first 'Grid' markup. Something like this?
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
Then you have to somehow refer to the Datacontext of the window. You can do it this way
<ComboBox
ItemsSource="{Binding DataContext.BoardNameList, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}}" />
if the name of your view is not 'MainWindow', you have to change it to the view name where you have that code.
One of the best ways is to create a UserControl for each model and then put data templates in TabControl.Resources with DataType specified for all types you could put in ItemsSource - you get full customization of the view with nice seperation of XAML files.
<Grid>
<TabControl Background="#FF292929" ItemsSource="{Binding AllExecuteButtonInfos}">
<TabControl.Resources>
<DataTemplate DataType="{x:Type MyViewModel1}">
<MyViewModel1_View ViewModel="{Binding}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type MyViewModel2}">
<MyViewModel2_View ViewModel="{Binding}"/>
</DataTemplate>
</TabControl.Resources>
</TabControl>
</Grid>
I'm going from memory, so the binding may be done differently, but that's the basic idea.
That, or you use some kind of ViewResolver as the only item in the TabControl (something like this)
Basically, go even more MVVM :)
Provided that the DataContext of your view is set correctly to your ViewModel and AllExecuteButtonInfos is indeed available in your view, you can use a RelativeBinding to access properties which are not in the DataContext of your current scope.
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Path=DataContext.BoardNameList}" />
With that, you are leaving the implicit DataContext of the DataTemplate, which is ActiveButton and access the object of the specified type via AncestorType. From there you can set a Path to the DataContext of the UserControl, which is, in your case, an object of the class ViewModel.
Imaging you are climbing up a ladder. From the ComboBox object up to your UserControl, from where you can access all underlying properties.

Force ViewModel Instantiation from XAML

I have a View OutputOptionsView which holds several UserControls with options settings which are displayed depending on the selection of a combobox.
I create the Datacontext and Datatemplates for the UserControls within OutputOptionsView like this:
<UserControl.Resources>
<ResourceDictionary>
<local:OutputOptionsViewModel x:Key="vm" />
<DataTemplate x:Key="OptionSettings1" DataType="{x:Type views:OptionSettings1View}">
<views:OptionSettings1View />
</DataTemplate>
<DataTemplate x:Key="OptionSettings2" DataType="{x:Type views:OptionSettings2View}">
<views:OptionSettings2View />
</DataTemplate>
....
</ResourceDictionary>
</UserControl.Resources>
The display of the OptionSettingsViews is handled as follows:
<ContentControl Name="OutputOptionsContentControl" Content="{Binding}" >
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource OptionSettings1}" />
<Style.Triggers>
<DataTrigger Binding="{Binding AvailableOptionsListSelectedIndex}" Value="1">
<Setter Property="ContentTemplate" Value="{StaticResource OptionSettings2}" />
</DataTrigger>
...
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
ItemsSource and SelectedIndex of the ComboBox are bound to the viewmodel class OutputOptionsViewModel of the OutputOptionsView:
<ComboBox Name="AvailableOptionsListComboBox" ItemsSource="{Binding AvailableOptionsList}" DisplayMemberPath="OptionTitle"
SelectedIndex="{Binding AvailableOptionsListSelectedIndex, UpdateSourceTrigger=PropertyChanged}"/>
Each of my OptionSettings view also gets a ViewModel:
<UserControl.Resources>
<ResourceDictionary>
<local:OptionSettings1ViewModel x:Key="vm" />
</ResourceDictionary>
</UserControl.Resources>
<Grid DataContext="{StaticResource vm}">
...
</Grid>
Now my Issue concerns the population of the population of the combobox. I created an Interface containing the OptionTitle which each OptionsSettingsViewModels inherits. AvailableOptionsList which is the ItemsSouce for the combobox is a List of this Interface.
public List<IOutputOption> AvailableOptionsList { get; set; }
It will be instantiated within the Constructor of the OutputOptionsViewModelclass.
Within each of the OptionSettingsViewModel class constructors I add the respective OptionsSettingsViewModel to this List:
public OptionSettings1ViewModel()
{
OutputOptionsViewModel.AvailableOptionsList.Add(this);
}
This leads to the following Problem: The combobox isn't populated as long as the OptionSettingsViews aren't instantiated, but they can't be instantiated , because they can't be selected from the empty combobox.
Therefore I'm looking to force the Instantiation of the OptionSettingsViews.
The comments made me think, there is some basic misunderstanding:
[Lynn Crumbling] I'd completely re-architect this to always instantiate all of the viewmodels, nesting them under the mainviewmodel. You're going to need them, so why not just spin them up in the ctor of the main vm?
and
[Roland Deschain] that is actually how I solved it at the moment, however this means that I have to set the datacontext in the code-behind of each optionssettingsviewmodel, which was what I wanted to avoid if possible
So, as Lynn said, you should start by registering the sub-viewmodels within the main viewmodel, no need for any view involvement at this point.
Then you can define DataTemplate for the viewmodels, not for the views as you do now.
<DataTemplate DataType="{x:Type viewmodels:OptionSettings1ViewModel}">
<views:OptionSettings1View />
</DataTemplate>
<DataTemplate DataType="{x:Type viewmodels:OptionSettings2ViewModel}">
<views:OptionSettings2View />
</DataTemplate>
By removing the x:Key and changing the DataType to the viewmodel type, the templates will be automatically selected to display content of the respective type.
The DataContext of your sub-views will be automatically set from the outside. Do not instantiate a sub-viewmodel within the controls xaml.
In your main OutputOptionsViewModel, you should host a collection of the sub-viewmodels. In your combobox, you should directly use this collection as itemssource.
Then just drop all the complicated template selection xaml and directly bind the content to your selected sub-viewmodel:
<ContentControl
Name="OutputOptionsContentControl"
Content="{Binding ElementName=AvailableOptionsListComboBox,Path=SelectedItem}" />

WPF, XAML: CustomControl like TextBox with caption

My attempt to create a CustomControl like a TextBox with variable caption.
I try to learn create CustomControl and my exceptation is that my CustomControl (I called it TextBoxCustomControl) has every properties and methods of TextBox and also has new property Caption.
I hope that is right, that my TextBoxCustomControl is inherited from TextBox and not from Control.
TextBoxCustomControl.cs
namespace CustomControlProject
{
public class TextBoxCustomControl : TextBox
{
public static readonly DependencyProperty CaptionProperty = DependencyProperty.Register("Caption", typeof(string), typeof(TextBoxCustomControl), new FrameworkPropertyMetadata(String.Empty));
public string Caption
{
get { return (string)GetValue(CaptionProperty); }
set { SetValue(CaptionProperty, value); }
}
static TextBoxCustomControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(TextBoxCustomControl), new FrameworkPropertyMetadata(typeof(TextBoxCustomControl)));
}
}
}
Themes\Generic.xaml (specific design for TextBoxCustomControl) -- there is the TextBox called innerTextBox
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CustomControlProject">
<Style x:Name="CustomStyle" TargetType="local:TextBoxCustomControl" BasedOn="{StaticResource {x:Type TextBox}}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:TextBoxCustomControl}">
<WrapPanel Orientation="Vertical">
<TextBox x:Name="innerTextBox" />
<Label Content="{TemplateBinding Caption}" />
</WrapPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
In MainWindow.xaml I use my TextBoxCustomControl and specific some properties for it.
<Window x:Class="CustomControlProject.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CustomControlProject"
Title="MainWindow" Height="350" Width="525">
<WrapPanel>
<local:TextBoxCustomControl Width="287" Background="Yellow" Caption="Fill the nickname, please." />
</WrapPanel>
</Window>
My expectation is that innerTextBox inside my TextBoxCustomControl inherit all properties (such like background, width, etc.), but that is not happened. What I do wrong?
First off, there's a difference between a CustomControl, inheriting from a Control, and an UserControl composed out of several Controls.
The Style you're applying is overriding all the TextBox's properties. When you set a Template, you need to use TemplateBinding for all the properties that you want to bind against later. If not, they will be no longer accessible from the outside.
For what you're trying to achieve, you can skip the Style and change XAML to
<Window x:Class="CustomControlProject.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CustomControlProject"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<local:TextBoxCustomControl
x:Name="CustomBox"
Width="287" Background="Yellow"
Caption="Fill the nickname, please." />
<Label
Content="{Binding ElementName=CustomBox, Path=Caption}" />
</StackPanel>
</Window>
To make it all-in-one, you'd need to build a UserControl.
First of all, what you are making is a composite control. So, inheriting it from TextBox doesn't make any sense. Inheriting from TextBox would make sense if you are improving a normal TextBox with additional capabilities.
Your thinking that whatever you set at your custom control level will be inherited by child controls is wrong. If you want to do that, use TemplateBinding for individual properties. Eg; <TextBox x:Name="innerTextBox" Width="{TemplateBinding Width}"/> .
Note : Some properties (FontSize, FontFamily) are propagated anutomatically without any extra work.
What you are trying to make is already present as <HeaderedContentControl/> . You can study its source code here or using ILSpy.
Sample :
<HeaderedContentControl>
<HeaderedContentControl.Header>
<Border>
<TextBlock Text="Name please !"/>
</Border>
</HeaderedContentControl.Header>
<HeaderedContentControl.Content>
<TextBox />
</HeaderedContentControl.Content>
</HeaderedContentControl>

WPF Determining DataTemplate object a user clicked on in ItemsControl

Assume a simple MVVM Application implemented in WPF:
There is a view with an ItemsControl that uses DataTemplates to resolve a view for various ViewModels in a collection.
What I want to know is, how would I add functionality to allow me to click on a given ViewModel in my ItemsControl to return that element in the container ViewModel?
That is to say, for the given example, I may want be able to click on my WrapPanel and then from my BarnYardViewModel have the particular ViewModel from the ItemSource tag returned. (much like binding the selected item in a ComboBox)
<UserControl x:Class="AGI_ServiceTool.View.DataMonitorView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:vm="clr-namespace:MyProject.ViewModel">
<UserControl.Resources>
<DataTemplate DataType="{x:Type vm:MooCowViewModel}">
<StackPanel>
<Image Source="/View/Images/MooCow.png"/>
<label content="This is a Moo Cow"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:ChickenViewModel}">
<StackPanel>
<Image Source="/View/Images/Chicken.png"/>
<label content="This is a Chicken"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:HorseViewModel}">
<StackPanel>
<Image Source="/View/Images/SarahJessicaParker.png"/>
<label content="This is a Horse"/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding MyAnimals}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" ScrollViewer.CanContentScroll="True" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
</UserControl>
And a few basic ViewModels:
namespace MyProject.ViewModel
{
class AbstractViewModel : INotifyPropertyChanged { ... }
class MooCowViewModel : AbstractViewModel {}
class ChickenViewModel : AbstractViewModel {}
class HorseViewModel : AbstractViewModel {}
class BarnYardViewModel : AbstractViewModel
{
public BarnYardViewModel()
{
_myAnimals.add(new MooCowViewModel());
_myAnimals.add(new ChickenViewModel());
_myAnimals.add(new HorseViewModel());
}
private ObservableCollection<AbstractViewModel> _myAnimals = new ObservableCollection<AbstractViewModel>();
public ICollectionView MyAnimals{
get { return System.Windows.Data.CollectionViewSource.GetDefaultView(_myAnimals); }
}
}
}
I would use a regular Button styled to have no UI, and would pass the ViewModel to it using the CommandParameter
For example,
<ControlTemplate x:Key="ContentOnlyTemplate" TargetType="{x:Type Button}">
<ContentPresenter />
</ControlTemplate>
...
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Command="{Binding ElementName=MyItemsControl, Path=DataContext.MyClickCommand}"
CommandParameter="{Binding }"
Template="{StaticResource ContentOnlyTemplate}"
Content="{Binding }" />
</ItemsPanelTemplate>
</ItemsControl.ItemTemplate>
This will result in each item in your ItemsControl being rendered using a Button, and the Button will execute whatever command you specify and pass the current ViewModel in as the CommandParameter.
In this case, I've specified that the Button.Template should be overwritten to use a ContentPresenter which has no UI, so basically it will be whatever your DataTemplate for each animal is.
There are some other solutions posted at this related WPF question too if you're interested: What is the best way to simulate a Click, with MouseUp & MouseDown events or otherwise?
Any reason why it is a StackPanel yet you want something to know if it is Clicked?
I suggest you to change it to a Button and change the Template for it then hookup the Command property to a property in your ViewModel then said the CommandParamter as the Type

Categories

Resources