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.
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 working on a WPF application that is using styles and templates to dynamically build a ribbon menu, here are the relevant snippets:
<RibbonTab x:Name="HomeTab" Header="Home" HorizontalAlignment="Stretch" ItemContainerStyle="{StaticResource GroupStyle}" ItemsSource="{Binding Groups}"/>
<Style TargetType="RibbonGroup" x:Key="GroupStyle">
<Setter Property="Header" Value="{Binding Header}" />
<Setter Property="ItemsSource" Value="{Binding Controls}"/>
<Setter Property="ItemTemplate" Value="{StaticResource RibbonButtonTemplate}"/>
</Style>
<DataTemplate x:Key="RibbonButtonTemplate" DataType="{x:Type enums:RibbonControlConfig}">
<RibbonButton Label="{Binding Header}" LargeImageSource="{Binding Image}" Command="{Binding Command}"/>
</DataTemplate>
So now Resharper is complaining about the bindings within the GroupStyle since they are actually elements of a child property of the view model, as can be seen by the ItemSource binding on the RibbonTab.
This does of course function just fine, I would just prefer to have both no complaints from ReSharper and access to accurate Intellisense, so is there anyway to tell the style what it's context is?
Side note: I did first try to do this setup by using nested data templates but I wasn't able to get anything passed the first level template to work.
No, it's not possible to specify the DataContext type in a style. In general, you shouldn't use bindings in a style anyway, because a style is about appearance, not about data. It's especially important to follow this rule if you plan to make your control reusable: you can't make assumptions about what it will be bound to.
I have a ListView bound with an ObservableCollection. The Itemtemplate of the ListView is the following:
<ListView ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True" PreviewMouseLeftButtonUp="ListViewClicked" PreviewKeyDown="NameBox_KeyDown">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Source="./Resources/DeleteIcon.png"/>
<TextBox Text="{Binding Path=Name}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
This way I give the user the choice to either change the name of the element, or delete it from the list. I handle this two operations in code behind through the two event handlers attached to the ListView.
The problem is that when I click on the TextBox, the SelectedItems property of the ListView doesn't change and it points the last selected item. When I click on the image or on the free space around the selection change.
Now I have two questions:
Is there an easy way to fix this behavior?
Is it possible to get a reference to the collection item whose property is exposed by the TextBox?
One way to deal with this problem is to set Trigger to set IsSelected when keyboard focus is within ListViewItem
<ListView ... SelectionMode="Single">
<!-- .... -->
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Style.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="IsSelected" Value="True"/>
</Trigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
</ListView>
if I'm using a ListBox for data binding, the listbox generates a listboxitem for each item, same goes for combo box and comboBoxItem.
My question is - how do i set it myself for a given ItemsControl? (e.g. make the containing element be Border)?
The default item used to wrap each item is a ContentPresenter
I am not sure why you'd want to overwrite this, since it has no visual appearance or specific behavior that would mess with your UI.
You can set the ItemTemplate if you want to wrap each item in a Border object
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Blue" BorderThickness="2">
<TextBlock Text="{Binding }" />
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
Or set the ItemContainerStyle if you want to apply any specific style to the ContentPresenter
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Grid.Column" Value="{Binding ColumnIndex}" />
<Setter Property="Grid.Row" Value="{Binding RowIndex}" />
</Style>
</ItemsControl.ItemContainerStyle>
After digging a little with ILSpy -
Apparently The magic is done in
protected override DependencyObject GetContainerForItemOverride()
{
return new ListBoxItem();
}
This is where ListBox does it - and in my control I should override this as well.
I have got ComboBox which is populated with collection of customTypes. On comboBox change, I would like to load/change content in the particular region so that it loads related data for the selected ComboBox item (it could be in the form of loading userControl or I i dont mind specifying DataTemplate for it).
It is similar to this question WPF Load Control question. But in this question, he is talking about individual DataTemplates in the actual Listbox, while I am talking about populating certain window region on ComboBox change.
I am using MVVM (and not PRISM) .Net 3.5.
U could use ContentControl which is the placeholder for the actual Content that is dynamically decided as per combobox selection.
The follwoing code is just for guidance
<Window ...>
<Window.Resources>
<MyView1 x:Key="MyView1" />
<MyView2 x:Key="MyView2" />
</Window.Resources>
...
<ComboBox x:Name="MyCombo">
<ComboBox.ItemsSource>
<sys:String>"MyView1"</sys:String>
<sys:String>"MyView2"</sys:String>
....
</ComboBox.ItemsSource>
</ComboBox>
...
<!-- This is where your region is loaded -->
<ContentControl>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=SelectedItem,
ElementName=MyCombo}"
Value="MyView1">
<Setter Property="Content"
Value="{StaticResource MyView1}"
</DataTrigger>
<DataTrigger Binding="{Binding Path=SelectedItem,
ElementName=MyCombo}"
Value="MyView2">
<Setter Property="Content"
Value="{StaticResource MyView2}"
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</Window>
The data loading can be part of the MyView1 and MyView2 user control's constructor or your main UI's data context view model.
As far as I understand the question is how to change underlying data being bound to UI and not a DataTemplate only.
You can use EventSetter which will be handled in code behind where you can switch DataContext for a region you've mentioned:
<ComboBox>
<ComboBox.Resources>
<Style TargetType="ComboBoxItem">
<EventSetter Event="Selector.SelectionChanged"
Handler="YourHandler"/>
</Style>
</ComboBox.Resources>
</ComboBox>
But from MVVM perspectives it could be not perfect solution so you can introduce your own ComboBox class wich is Command aware, see this SO post: WPF command support in ComboBox
In this way you can decouple logic from UI using Command.