ListBox with DoubleClick on Items using DataTemplate - c#

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...
}

Related

Scroll to specific item on ItemControls on WPF MVVM

I want to search an item in an ItemsControl and scroll to the specific item that I'm looking for.
This is my XAML code:
<ItemsControl Grid.Row="2" x:Name="MessagesItemsCtrl" Grid.Column="1" Margin="0,5,0,0" ItemsSource="{Binding NewChatter}" ItemTemplate="{DynamicResource MessagesDataTemplate}" AllowDrop="True" Drop="MessagesItemsCtrl_Drop"
ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.HorizontalScrollBarVisibility="Auto">
<ItemsControl.Template>
<ControlTemplate TargetType="ItemsControl">
<ScrollViewer x:Name="myscroll" ScrollChanged="MessagesItemsCtrl_ScrollChanged">
<ItemsPresenter/>
</ScrollViewer>
</ControlTemplate>
</ItemsControl.Template>
</ItemsControl>
Does anyone have suggestions for my problem?
Don't add the ScrollViewer to the ItemsControl. Simply use the ListBox instead.
You can search for an item by iterating over the source collection (NewChatter in your case).
To scroll the selected item into the view:
Add a binding to SelectedItem.
Set the source property of this binding to update the ListBox.SelectedItem (a Binding set on this property binds TwoWay by default).
This will trigger the SelectionChanged event of the ListBox.
Call ListBox.ScrollIntoView() to scroll any item into the view.
MainWindow.xaml
<ListBox ItemsSource="{Binding NewChatter}"
SelectedItem="{Binding SelectedNewChatterItem}"
OnSelectedItemChanged="Selector_OnSelectionChanged" />
MainWindow.xaml.cs
private void Selector_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var listBox = sender as ListBox;
listBox.ScrollIntoView(listBox.SelectedItem);
}

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

WPF Binding ListBox and TabItems

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.

Nested DataTemplates in ListBox

Is there any way to make a DataTemplate reference itself just from XAML? In this particular case trying to reference a DataTemplate from a ListBox contained within the same DataTemplate. Here is the solution I'd like, which doesn't work.
<DataTemplate x:Key="nestedItem" DataType="{x:Type local:NestedItem}">
<Expander Header="{Binding Path=Name}">
<ListBox ItemsSource="{Binding Path=Items}" x:Name="itemsList"
ItemTemplate="{StaticResource nestedItem}"/>
</Expander>
</DataTemplate>
And here's the solution I am currently using, which works.
<DataTemplate x:Key="nestedItem" DataType="{x:Type local:NestedItem}">
<Expander Header="{Binding Path=Name}" Expanded="OnItemExpanded">
<ListBox ItemsSource="{Binding Path=Items}" x:Name="itemsList"/>
</Expander>
</DataTemplate>
With code behind:
private void OnItemExpanded(object sender, RoutedEventArgs e)
{
if (e.OriginalSource != sender) return;
var source = (Expander) sender;
ListBox listBox = source.FindName("itemsList") as ListBox;
NestedItem item = source.DataContext as NestedItem;
listBox.ItemsSource = item.Items;
listBox.ItemTemplate = (DataTemplate) FindResource("nestedItem");
}
If you change your inner reference to be a DynamicResource instead of a StaticResource then it will work as you want. This is because there are some differences in how a StaticResource and DynamicResource actually look for the Resource item.
<DataTemplate x:Key="Local_NestedItem"
DataType="{x:Type local:NestedItem}">
<Expander Header="{Binding Path=Name}">
<ListBox ItemsSource="{Binding Path=Items}"
x:Name="itemsList"
ItemTemplate="{DynamicResource Local_NestedItem}" />
</Expander>
</DataTemplate>
Also, if you don't mind using some code, another good option is to use a DataTemplateSelector
Did you try using HierarchicalDataTemplate instead of DataTemplate for your first solution?
Did not test it for your case, but for treeviews it usually works that way.

Why are tab headers displayed in the content area of tabs in a XAML TabControl?

I've got a TabControl whose ItemsSource is bound to an observable collection of views (UserControls) each which have as its root element a TabItem. However, when it is displayed, the Header text is in content of each TabItem, as if UserControl wrapper is causing conflicts:
The TabControl is in SmartFormView.xaml:
<UserControl x:Class="TestApp.Views.SmartFormView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel
Margin="10">
<TextBlock Text="{Binding Title}"
FontSize="18"/>
<TextBlock Text="{Binding Description}"
FontSize="12"/>
<TabControl
Margin="0 10 0 0"
ItemsSource="{Binding SmartFormAreaViews}"/>
</StackPanel>
</UserControl>
What do I have to change so that TabItems are displayed as TabItems inside the TabControl?
Here are the TabItem views called SmartFormAreaView.xaml:
<UserControl x:Class="TestApp.Views.SmartFormAreaView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<TabItem Header="This is the header">
<StackPanel Margin="10">
<TextBlock Text="this is the content"/>
</StackPanel>
</TabItem>
</UserControl>
And here is where I create and load each view into the ObservableCollection:
var areas = from area in xmlDoc.Descendants("area")
select area;
foreach (var area in areas)
{
SmartFormArea smartFormArea = new SmartFormArea();
smartFormArea.IdCode = area.Attribute("idCode").Value;
smartFormArea.Title = area.Attribute("title").Value;
SmartFormAreaPresenter smartFormAreaPresenter = new SmartFormAreaPresenter(smartFormArea);
SmartFormAreaViews.Add(smartFormAreaPresenter.View as SmartFormAreaView);
}
For any ItemsControl, if the items added to its Items collection (either directly or via ItemsSource) are not instance of that control's item container, then each item is wrapped in an instance of the item container. The item container is a class such as TabItem or ListBoxItem. The item container is normally a ContentControl or HeaderedContentControl, and your actual item is assigned to its Content property, so you can use templates etc to control how the content is presented. You can also style the item container itself using the ItemControl's ItemContainerStyle property.
In this particular case, you should bind ItemsSource to a list of SmartFormAreaPresenters. Then use something like this for the tab control:
<TabControl ItemsSource="{Binding SmartFormAreaPresenters}">
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding HeaderText}" />
</Style>
</TabControl.ItemContainerStyle>
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type local:SmartFormAreaPresenter}">
<local:SmartFormAreaView />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
where HeaderText is a suitable property on your SmartFormAreaPresenter. You should also remove the TabItem from your SmartFormAreaView definition. The DataContext of each View will automatically be set to the appropriate Presenter.
See Dr. WPF's blog for an excellent discussion of various ItemsControl related topics.
The TabControl will accept your controls as its controls only if they can be cast to TabItem, not UserControl, or SmartFormAreaView, etc.
So you either fill regular TabItems with your visual tree, or you subclass TabItems, or you subclass the TabControl to override its IsItemItsOwnContainerOverride method, to accept your type as the container.
The method should look as follows:
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is YourControlTypeHere || item is TabItem;
}

Categories

Resources