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}" />
Related
I am working on WPF application using MVVM. I have two page. I have multiple UserControls in a page 1, on selection of UserControls from page 1, I want to show that selected userControl in 2nd page. Below are my code.
ViewModel Code
public RelayCommand<string> OnClickSelectWidgetCommand => new RelayCommand<string>((setUserControlName) =>
{
using (new CursorWait())
{
var MyContentControl = setUserControlName;
MessageBox.Show(MyContentControl);
//How to render UserControl to View?
}
}, true);
Here in above code I get the UserControl name in setUserControlName variable. Now how to bind that UserControl to XAML page? Below are my code that I have tried.
View Code
<StackPanel Background="Black" VerticalAlignment="Top">
<Border Name="UserControl1BorderLow" BorderBrush="White" BorderThickness="0" >
<ItemsControl ItemsSource="{Binding LowCollection}" Margin="4,0" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel HorizontalAlignment="Left" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<controls:UserControlColumn1XL HorizontalAlignment="Left" Margin="2" />
<!--what can I do here in above line to make it dynamically render the userControl in place of UserControlColumn1XL-->
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border></StackPanel>
Above code, In DataTemplate what need to be change to bind UserControls dynamically?
There are two ways to solve this, one involves setting the template based on your data type (DataTemplates) and the second involves setting it based on the data itself (DataTriggers).
In the first case your LowCollection should be an array of objects, or some base class that your view models are all derived from (ViewModel1, ViewModel2 etc). In this case you can get rid of your itemtemplate altogether and just add DataTemplates to specify how each of the items in your ItemsControl should be represented:
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type local:ViewModel1}">
<UserControl1 />
</DataTemplate>
<DataTemplate DataType="{x:Type local:ViewModel2}">
<UserControl2 />
</DataTemplate>
... etc...
In the second case you need to set a template based on the value of some property in your view model. In this case you do need to set the ItemTemplate, and you give it a Style which uses data triggers to set an appropriate DataTemplate:
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding}">
<ContentPresenter.Style>
<Style TargetType="{x:Type ContentPresenter}">
<Style.Triggers>
<DataTrigger Binding="{Binding YourProperty}" Value="YourValue1">
<Setter Property="ContentTemplate" Value="{StaticResource YourDataTemplate1}" />
</DataTrigger>
<DataTrigger Binding="{Binding YourProperty}" Value="YourValue2">
<Setter Property="ContentTemplate" Value="{StaticResource YourDataTemplate2}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentPresenter.Style>
</ContentPresenter>
</DataTemplate>
</ItemsControl.ItemTemplate>
The relevant parts to note here are that there is a property in your view model called YourProperty which can have two values i.e. YourValue1 or YourValue2; the style above then selects either YourDataTemplate1 or YourDataTemplate2, depending on the value of YourProperty.
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); }
}
}
at the moment in order to fix a bug from telerik, my ItemsSource must be pointing to the viewmodel I'm currently working with.
Relationship.xaml
<UserControl.Resources>
<Client:PersonViewModel x:Key="MyViewModel"/>
</UserControl.Resources>
Where it's used.
<Telerik:GridViewComboBoxColumn Header="Relationship"
ItemsSource="{Binding GridRelationshipTypes, Mode=TwoWay, Source={StaticResource MyViewModel}}"
DataMemberBinding="{Binding RelationshipType}"
SelectedValueMemberPath="Id"
DisplayMemberPath="Name"
IsReadOnly="False"/>
I have four other view models this logic needs to be applied to. I don't want to create 5 different UserControls for such a small thing. I'm wondering if I can create a method such that it'll check what the current viewmodel type is and will use the corresponding viewmodel.
PseudoCode - ViewModelTypes is an enum.
public void StaticResourcToUse(ViewModelTypes viewModelType)
{
if (viewModelType == ViewModelTypes.PersonViewModel)
use personviewmodel resources
if (viewModelType == ViewModelTypes.BusinessViewModel)
use businessViewModel resources
}
If I understand correctly what you want is switch your view based on view model.
Use a ContentControl to display the data, and swap out the ContentTemplate in a trigger based on the property that changes.
Here's an example in Rachel Lim's blog that swaps a template based on a bound property:
<DataTemplate x:Key="CarTemplate" TargetType="{x:Type local:YourViewModel}">
<TextBlock Text="I'm a Car" />
</DataTemplate>
<DataTemplate x:Key="TrackTemplate" TargetType="{x:Type local:YourViewModel}">
<TextBlock Text="I'm a Track" />
</DataTemplate>
<DataTemplate DataType="{x:Type local:YourViewModel}">
<ContentControl Content="{Binding }">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource CarTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding YourType}" Value="Track">
<Setter Property="ContentTemplate" Value="{StaticResource TrackTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
I have a problem with the WPF tab control.
I have a TabControl, with the ItemsSource bound to an ObservableCollection. I created a data template for the header/content portion of the tabs. The content portion contains a custom control, with a bunch of labels and text boxes. For the text boxes that are editable when a new tab is created that data carries over and appears in the new tab. Not sure if it's a problem with my XAML or something in the view model. Here's my code for the XAML:
<UserControl.Resources>
<DataTemplate x:Key="TabItemHeaderTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding AdFile.Name}" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="TabItemContentTemplate">
<MyView:MyCustomControl/>
</DataTemplate>
<Style x:Key="TabItemContainerStyle" TargetType="TabItem">
<Setter Property="Header" Value="{Binding}"/>
<Setter Property="HeaderTemplate"
Value="{StaticResource TabItemHeaderTemplate}"/>
<Setter Property="Content" Value="{Binding}"/>
<Setter Property="ContentTemplate"
Value="{StaticResource TabItemContentTemplate}"/>
</Style>
</UserControl.Resources>
<TabControl Grid.Row="3" ItemsSource="{Binding OpenedFiles}" x:Name="_myTabControl" SelectedItem="{Binding Path=CurrentDataControlViewModel, Mode=TwoWay}" SelectionChanged="TabControlSelectionChanged" ItemContainerStyle="{StaticResource TabItemContainerStyle}"/>
Not sure what other information I should provide. Maybe this is a common problem and I am just not setting something up correctly? Basically I just want to be able to create a new instance of the control for every tab...
Thanks in advance.
It sounds as though your ViewModel is a Singleton, being cloned, or you're trying to populate the new tab with the existing ViewModel.
If you're using MEF, remember to set the [PartCreationPolicy] attribute to NonShared.
I've done this with list view in that I can define a resource:
<UserControl.Resources>
<GridView x:Key="MyGrid" x:Shared="False">
<!-- Defines what's in the grid view -->
</GridView>
</UserControl.Resources>
Then I can have two views using the same Grid:
<ListView View="{DynamicResource MyGrid}" ItemsSource="{Binding Path=TodaysItems}"/>
<ListView View="{DynamicResource MyGrid}" ItemsSource="{Binding Path=TomorrowsItems}"/>
I'm trying to do the same thing with TreeViews. I've defined my tree view:
<UserControl.Resources>
<TreeView x:Key="MyTreeView" x:Shared="False">
<!-- Defines what's in the Tree view -->
</TreeView>
</UserControl.Resources>
But I can't find what I need to do
<TreeView ???="{DynamicResource MyTreeView}" ItemsSource="{Binding Path=ClientData}"/>
<TreeView ???="{DynamicResource MyTreeView}" ItemsSource="{Binding Path=CustomerData}"/>
Can I even do this?
You cannot do this with TreeView. The reason why you can do it with the ListView is that it has a property View that can be set to different views. The view in this case is not a standalone UI element - it just, lets say, "settings" for the ListView. While the TreeView is a UI element just like ListView.
The command approach to re-use in XAML is Styles. You can define a style for you TreeView where you can define common properties and then apply it to as many elements as you like.
Here is an example how you can define a style:
<Style x:Key="MyTreeStyle"
TargetType="{x:Type TreeView}">
<Setter Property="Background"
Value="Red"/>
<!-- Other property setters go here -->
</Style>
And here is how you apply it:
<TreeView Style="{StaticResource MyTreeStyle}" ItemsSource="{Binding Path=ClientData}"/>
<TreeView Style="{StaticResource MyTreeStyle}" ItemsSource="{Binding Path=CustomerData}"/>