WPF Binding ListBox and TabItems - c#

New to WPF, am trying to do something basic (I think!). I have a TabControl and a ListBox that shows what tabitems are open:
<ListBox Width="170" Height="188" ItemsSource="{Binding Items, ElementName=tabControl}" Name="ListTabs" Canvas.Left="0" Canvas.Top="27">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}" BasedOn="{StaticResource {x:Type ListBoxItem}}">
El
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Is it possible to bind to specific tabitems (tabitem2 and tabitem3) rather than the whole tabcontrol? Reason being is the first tabitem1 is a welcome tab and I don't want it to be shown in the listbox.
UPDATE:
Would someone be so kind to post some code on how to use an IValueConverter to hide/filter a tabitem? I have been searching for hours with no luck. Many many thanks!

In your current set up the only way would be to run it through an IValueConverter.
<Window.Resources>
<converters:StripOutFirstTabConverter x:Key="StripOutFirstTabConverter"/>
</Window.Resources>
<ListBox Width="170" Height="188" ItemsSource="{Binding Items, ElementName=tabControl, Converter={StaticResource StripOutFirstTabConverter}}" Name="ListTabs" Canvas.Left="0" Canvas.Top="27">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}" BasedOn="{StaticResource {x:Type ListBoxItem}}">
El
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
If you were willing to modify your approach you could bind the ListBox.ItemsSource to a ICollectionView and then make use of the Filter property.
public ICollectionView Tabs
{
get
{
if (_view == null)
{
_view = CollectionViewSource.GetDefaultView(tabControl.Items);
_view.Filter = Filter;
}
return _view;
}
}
private bool Filter(object arg)
{
//arg will be a TabItem, return true if you want it, false if you don't
}

you can add a converter to ItemSource and then in the converter remove the welcome page or do any changes that you want .

I recommend not doing this. Use a common data source instead with both Listbox and Tabcontrol.
To filter/intercept any data binding, you can use IValueConverter.

You would have to filter out the welcome tab so you will need to add a Filter on a CollectionView Instead of binding to the items of the tab control you'd bind to the collectionview.
Although a ValueConverter might work, I consider that a kind of hack.

Related

Set Header of TabItem

I have a TabControl which is bound to a List of UserControls (MyControls)
<TabControl Background="{x:Null}" x:Name="MyView" ItemsSource="{Binding MyControls}" >
I want to bind the header of each tab item to a property(Title) in each UserControl. Which I did as below
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding Title}"/>
</Style>
</TabControl.ItemContainerStyle>
However since I override the ItemContainerStyle I lost all the default style for the application. My tab header looks different from other tab headers in the application
Is there any way to just bind to the Title without changing any style?
Define an ItemTemplate:
<TabControl Background="{x:Null}" x:Name="MyView" ItemsSource="{Binding MyControls}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}" />
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
MyControls shouldn't return an IEnumerable<UserControl> though. It should return an IEnumerable<YourObject> where YourObject is a POCO class with a Title property along with any other properties. You should then use DataTemplates to define the appearance of a YourObject.

MVVM Hooking up nested child views to child view model

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); }
}
}

Get the selected items of a ListView (MVVM, Caliburn.Micro)

What is the situation:
I'm working on a C# WPF application with Caliburn.Micro. I am using the MVVM pattern.
I have a ListView with a ContentControl as ItemTemplate. The ListView's ItemsSource is bound to a List (ObservableCollection) of ViewModels in the corresponding ViewModel.
<ListView ItemsSource="{Binding ViewModelList}" SelectionMode="Extended">
<ListView.ItemTemplate>
<DataTemplate>
<ContentControl cal:View.Model="{Binding}" />
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
What I want:
I want to get the selected items (all of them) of the ListView, but I don't know how without violating the MVVM pattern.
It would be nice to have a property "IsSelected" in the ViewModels that are presented by the ContentControl and to bind that somehow to my ListView.
Is it possible to do that or is there another/a better way?
Update:
It was easier than expected. I added a property public bool IsSelected { get; set; } in my ViewModel and put this inside the ListView Control:
<UserControl.Resources>
<Style TargetType="{x:Type ListViewItem}" BasedOn="{StaticResource {x:Type ListViewItem}}">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</UserControl.Resources>
Now I can get the selected items with:
foreach (var item in ViewModelList)
{
if (item.IsSelected)
{
// Do stuff
}
}
<Grid>
<Grid.Resources>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=OneWayToSource}"/>
</Style>
</Grid.Resources>
<ListView ItemsSource="{Binding ViewModelList}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding IsSelected}"/>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal">
</StackPanel>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
</Grid>
This is probably the code you're looking for. If you're using a multiple selection list view, you could get all the selected items with below code.
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding SelectionChanged}" CommandParameter="{Binding Path=SelectedItems, ElementName=ListViewName}" />
</i:EventTrigger>
</i:Interaction.Triggers>
Bind it to a command in the viewmodel.
private ICommand selectionChanged;
public ICommand SelectionChanged
{
get { return selectionChanged; }
set { SetProperty(ref selectionChanged, value); }
}

WPF Databinding TabItem Headers

I am binding a ObservableCollection of data objects to my tab control item source. I have correctly figured out how to bind the controls within the tabitem that is generated, however I cannot figure out how to change the header property of the tabitem that is generated using the a property within the Observable Collection. Sorry if I am wording this incorrectly. Here is my XAML for the tabitem data template:
<DataTemplate x:Key="TabItemTemplate">
<TreeView Height="461" VerticalAlignment="Top"
Width="625" ItemTemplateSelector="{StaticResource TreeviewDataSelector}" ItemsSource="{Binding}" />
</DataTemplate>
Create a Style for your TabItems that sets the Header property, and apply the style to TabControl.ItemContainerStyle
<TabControl>
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding PathToYourProperty}"/>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
Set the DisplayMemberPath on the TabControl to the name of the property.
<TabControl ItemsSource="{Binding items}" DisplayMemberPath="headerPropertyName">

ListBox with DoubleClick on Items using DataTemplate

I want to know if a double-clicking functionality for a ListBox can easily be build. I have a ListBox with a collection as ItemSource. The collection contains own data-types.
<ListBox ItemsSource="{Binding Path=Templates}"
ItemTemplate="{StaticResource fileTemplate}">
I defined a DataTemplate for my Items, which consists of StackPanels and TextBlocks.
<DataTemplate x:Key="fileTemplate">
<Border>
<StackPanel>
<TextBlock Text="{Binding Path=Filename}"/>
<TextBlock Text="{Binding Path=Description}"/>
</StackPanel>
</Border>
</DataTemplate>
Now I want to detect a double-click-event for the double-clicked list-item. Currently I tried with following, but it doesn't work because it doesn't return the Item bound to the ListBox but the TextBlock.
if (TemplateList.SelectedIndex != -1 && e.OriginalSource is Template)
{
this.SelectedTemplate = e.OriginalSource as Template;
this.Close();
}
What is a clean way to handle a double-click-event on an item in a ListBox, if the icons are not ListBoxItems, but own DataTemplates?
I've been playing around with this and I think I've got there...
The good news is, that you can apply a style to your ListBoxItem and apply a DataTemplate - the one does not preclude the other...
In other words, you can have something like the following:
<Window.Resources>
<DataTemplate x:Key="fileTemplate" DataType="{x:Type local:FileTemplate}">
...
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox ItemsSource="{Binding Templates}"
ItemTemplate="{StaticResource fileTemplate}">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<EventSetter Event="MouseDoubleClick" Handler="DoubleClickHandler" />
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</Grid>
and then implement a handler in your Window, like
public void DoubleClickHandler(object sender, MouseEventArgs e)
{
// item will be your dbl-clicked ListBoxItem
var item = sender as ListBoxItem;
// Handle the double-click - you can delegate this off to a
// Controller or ViewModel if you want to retain some separation
// of concerns...
}

Categories

Resources