How to handle MouseDown in a ListBox containint several elements - c#

I have a ListBox that looks like this:
<ListBox Name="ListBoxUsers" ItemsSource="{Binding Path=TempObservableUsers, ElementName=MainWindow, NotifyOnSourceUpdated=True}" ScrollViewer.HorizontalScrollBarVisibility="Hidden" ScrollViewer.VerticalScrollBarVisibility="Hidden" />
This ListBox shows, as you can see, a list of users comming from an ObservableCollection called TempObservableUsers. This part works fine.
Each element of the list looks like this:
As you can see, I modified my ItempsPanelTemplate to look differently. Here I attach my code:
<Style TargetType="ListBox" x:Key="VerticalListBox">
<Setter Property="Margin" Value="0,0,10,0"></Setter>
<Setter Property="Background" Value="Transparent" />
<Setter Property="VerticalContentAlignment" Value="Top" />
<Setter Property="IsTabStop" Value="False" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="BorderBrush" Value="{DynamicResource RegularBlue}" />
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
<Setter Property="Width" Value="450" />
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" Width="450" />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Vertical" Width="450" Margin="0,5" >
<StackPanel Orientation="Horizontal" Background="{DynamicResource TransparentMainBlue}" >
<Image Width="70" Height="70" Margin="5" Source="../images/delete.png" />
<StackPanel Orientation="Vertical" Width="285" >
<StackPanel Orientation="Horizontal">
<Label Content="NAME:" FontWeight="Bold"/>
<Label Content="{Binding Path=Name, FallbackValue=Name}" Foreground="White" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label Content="ID:" FontWeight="Bold"/>
<Label Content="{Binding Path=Identifier, FallbackValue=Identifier}" />
</StackPanel>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="5" HorizontalAlignment="Right" VerticalAlignment="Top">
<Image Source="../images/edit.png" Width="20" />
<Image Source="../images/detail.png" Width="20" />
<Image Source="../images/delete.png" Width="20" />
</StackPanel>
</StackPanel>
<Grid Height="30" Background="{DynamicResource RegularBlue}">
<Label Content="XXXX-XXXX-XXXX-XXXX" Foreground="White" HorizontalContentAlignment="Center" FontWeight="Bold" />
</Grid>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
Ok, after that:
As you can see, each item contains some images on top right corner. I want each one to do its job: edit, more, delete...
So I need to bind each image click with the corresponding item on the ListBox.
How can I bind each image click to do the corresponding action (edit, update, delete) and with its corresponding item on the ListBox?
EDIT: Now my code looks like this for each item template:
<StackPanel Orientation="Horizontal" Margin="5" HorizontalAlignment="Right" VerticalAlignment="Top">
<Button Command="{Binding DataContext.DeleteCommand}" CommandTarget="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ListBox}}" CommandParameter="{Binding}">
<Image Source="../images/delete.png" Width="20" />
</Button>
............
</StackPanel>
How to implement DeleteCommand?
PS: I'm not using MVVM pattern yet, as I'm not famliar to it. Only trying to bind that button commands i xaml directly from my ResourceDictionary.

If I were attempting to fulfil your requirements, then I would use Buttons with an Image inside instead of Images on their own. Next, I would use a form of RelayCommand to handle my ICommands in the view model. Using this method, passing the relevant item along to the ICommand is easy using the CommandParameter property:
<StackPanel Orientation="Horizontal" Margin="5" HorizontalAlignment="Right"
VerticalAlignment="Top">
<Button Command="{Binding DataContext.EditCommand, RelativeSource={RelativeSource
AncestorType={x:Type YourPrefix:ThisView}}}" CommandParameter="{Binding}">
<Image Source="../images/edit.png" Width="20" />
</Button>
<Button Command="{Binding DataContext.DetailCommand, RelativeSource={RelativeSource
AncestorType={x:Type YourPrefix:ThisView}}}" CommandParameter="{Binding}">
<Image Source="../images/detail.png" Width="20" />
</Button>
<Button Command="{Binding DataContext.DeleteCommand, RelativeSource={RelativeSource
AncestorType={x:Type YourPrefix:ThisView}}}" CommandParameter="{Binding}">
<Image Source="../images/delete.png" Width="20" />
</Button>
</StackPanel>
In order for this to work, it presumes:
1) You have set the DataContext of your Window or UserControl to an instance of a class that has ICommand properties named EditCommand, DetailCommand and DeleteCommand.
2) You replace the XAML Namespace Prefix value of YourPrefix to your own valid prefix.
3) You replace the imaginary ThisView name of the view where this code will reside with the actual name of the Window or UserControl where you add this code, eg. MainWindow.
As for how to use RelayCommands, that is another question and goes against the single question, single problem ethos of Stack Overflow. However, there are many tutorials on this. Here are some to help you on your way:
If you've not used ICommands before, then the Commanding Overview page on MSDN is an invaluable resource and a good introduction to them.
You'll find dozens of useful posts by searching Stack Overflow for RelayCommand.
You can find a clear implementation of this class in the Implementation RelayCommand question on the Visual Studio Forum.

Since there is no command binding available for Image, I'll demo same using a Button appearing as in image
Start by writing a style for a button, to remove the original appearance and leave with only what i want to show ( image in this case)
<Style x:Key="simpleButton" TargetType="Button">
<Setter Property="Width" Value="70"/>
<Setter Property="Height" Value="70"/>
<Setter Property="Margin" Value="5"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<ContentPresenter/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Then I'll define my datatemplate as
<DataTemplate>
<StackPanel Orientation="Vertical" Width="450" Margin="0,5" >
<StackPanel Orientation="Horizontal" Background="{DynamicResource TransparentMainBlue}" >
<Button Style="{StaticResource simpleButton}"
Command="{Binding DataContext.DeleteCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=LixtBox}}"
CommandParameter="{Binding}">
<Image Source="../images/delete.png"/>
</Button>
...
then I'll write a DeleteCommand in my view model an initialize it's implementation using SimpleCommand, RelayCommand etc.
public ICommand DeleteCommand { get; set; }
once this is all in place my button which is now showing an image will bind itself to the command and will push the current item as command parameter upon execution (which is click for button)

Related

Propagate ListBoxItem IsSelected trigger to child control

I'm developing a CheckedListBox with self removable ListBoxItems. The problem is that an item only gets checked if the user clicks on the CheckBox area, which is kind of awkward.
How do I create ListBoxItem triggers (IsSelected) to check the checkboxes on a "DataSourced" ListBox? Eg:
Below is my control (all other code have been omitted for brevity):
<ListBox x:Name="executors" ItemsSource="{Binding Executors}" HorizontalAlignment="Left" Height="121" Margin="23,19,0,0" VerticalAlignment="Top" Width="362">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Height" Value="30" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="30" />
</Grid.ColumnDefinitions>
<CheckBox Margin="4,8" IsChecked="{Binding Enabled}">
<ContentPresenter Content="{Binding Description}">
</ContentPresenter>
</CheckBox>
<Button Command="{Binding DataContext.RemoveExecutorCommand, ElementName=executors}" CommandParameter="{Binding}" Background="White" Height="22" Width="22" Grid.Column="1">
<Image Source="trash.png" Stretch="Fill" Width="14" Height="14" />
</Button>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
Executors is a ObservableCollection of Executor which has Enabled and Description as members.
For further reference for those who come across this same question, here's a working snippet I have done. I couldn't find any working sample anywhere, so this might be of much use.
The main idea here was to either propagate the selection event to the CheckBoxes, which sounds too much of a work, or to simple extend the CheckBox selection area to fit the ListBoxItem.
Below is a sample on how to achieve the second option:
<ListBox x:Name="executors" ItemsSource="{Binding Executors}" HorizontalAlignment="Left" Height="121" Margin="23,19,0,0" VerticalAlignment="Top" Width="362" ScrollViewer.VerticalScrollBarVisibility="Visible">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Height" Value="30"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="9*"/>
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<CheckBox Content="{Binding Description}" IsChecked="{Binding Enabled}" VerticalContentAlignment="Center" Margin="4,0"/>
<Button Command="{Binding DataContext.RemoveExecutorCommand, ElementName=executors}" CommandParameter="{Binding}" Width="21" Height="21" Background="White" Grid.Column="1">
<Image Source="trash.png" Stretch="Fill" Width="14" Height="14" />
</Button>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
This should produce the following:

WPF Particular TabItem Width Behaviour

I've this code with TabItems added Dynamycally in CodeBehind (C#):
<TabControl x:Name="tabList" Grid.Column="2" HorizontalContentAlignment="Stretch" Background="Transparent" SelectionChanged="tabList_SelectionChanged" IsSynchronizedWithCurrentItem="True">
<TabControl.Resources>
<DataTemplate x:Key="TabHeader" DataType="TabItem">
<DockPanel>
<Button x:Name="btnDelete" BorderThickness="0" Background="Transparent" DockPanel.Dock="Right" Margin="5,0,0,0" Padding="0" Click="btnDelete_Click" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type TabItem}}, Path=Name}">
<Image Source="myImage></Image>
</Button>
<ToggleButton x:Name="btnPin" Checked="btnDisablex" Unchecked="btnDisablex" BorderThickness="0" Background="Transparent" DockPanel.Dock="Right" Margin="5,0,0,0" Padding="0" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type TabItem}}, Path=Name}">
<ToggleButton.Style>
//Many Useless Things
</ToggleButton.Style>
</ToggleButton>
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type TabItem}}, Path=Header}" />
</DockPanel>
</DataTemplate>
<Style TargetType="TextBox">
//Other Many Useless Things
</Style>
</TabControl.Resources>
</TabControl>
And with the following Style in User Resources:
<Style TargetType="{x:Type TabItem}">
<Setter Property="AllowDrop" Value="True"/>
<EventSetter Event="PreviewMouseMove" Handler="TabItem_PreviewMouseMove"/>
<EventSetter Event="Drop" Handler="TabItem_Drop"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Grid>
<Border
Name="Border"
Margin="0,0,-4,0"
Background="White"
BorderBrush="#888"
BorderThickness="1,1,1,1"
CornerRadius="2,12,0,0">
<ContentPresenter x:Name="ContentSite"
VerticalAlignment="Center"
HorizontalAlignment="Center"
ContentSource="Header"
Margin="12,2,12,2"
RecognizesAccessKey="True"/>
</Border>
</Grid>
<ControlTemplate.Triggers>
//Many Useless MultiTrigger and Datatrigger
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The code is working fine, until I add more tabs and the window width is no longer enough, but the TabControl behaviour is strange: the TabItem width becomes larger and the first TabItems disappear.
I just want the TabItem width reduces or just that the olders "disappear" but keeping always the same width. Why this is not working?
This is the screenshot taken in normal conditions
Normal view working OK
Here it's clear the problem: when I add more tabs (or I reduce the window/grid width) the tab width becomes larger
Tab View Problem

WPF Adding a datatrigger to the ImageSource of a user control from another control

I have the following usercontrol:
<Border
Style="{StaticResource notificationBarBorderStyle}"
Height="21"
>
<StackPanel
Orientation="Horizontal"
Style="{StaticResource notificationBarStyle}"
>
<DockPanel>
<Image
Width="17"
Height="16"
Margin="4,0,11,0"
Source="{Binding ElementName=NotificationControl, Path=ImageSource}"
/>
<TextBlock
x:Name="notificationTextBlock"
VerticalAlignment="Center"
Style="{StaticResource textBlockStyle}"
Text="{Binding ElementName=NotificationControl, Path=Message}"
/>
</DockPanel>
</StackPanel>
</Border>
and then in another usercontrol i try to reference it like so:
<Controls:NotificationBarControl
Grid.Row="2"
Grid.Column="0"
DataContext="{Binding IncomingResult}"
Message="{Binding TaskResultsMessage}"
Visibility="{Binding Path=ShowTaskResults, Converter={StaticResource boolToHiddenVisibilityConverter}}"
Command="{Binding DisplayTaskError}"
ImageSource="{DynamicResource somePicture1}"
>
I want to be able to put a data trigger on the image source so that depending on the state of a boolean flag, a different image will appear (call it somePicture2). I do not really want to do much to change the control itself, as it is referenced a few times in a rather large project and I do not want to break anything.
You could set the Style property of the control to a Style with a DataTrigger that binds to your bool property:
<Controls:NotificationBarControl
Grid.Row="2"
Grid.Column="0"
DataContext="{Binding IncomingResult}"
Message="{Binding TaskResultsMessage}"
Visibility="{Binding Path=ShowTaskResults, Converter={StaticResource boolToHiddenVisibilityConverter}}"
Command="{Binding DisplayTaskError}">
<Controls:NotificationBarControl.Style>
<Style TargetType="Controls:NotificationBarControl">
<Setter Property="ImageSource" Value="{StaticResource somePicture1}" />
<Style.Triggers>
<DataTrigger Binding="{Binding YourBooleanSourceProperty}" Value="True">
<Setter Property="ImageSource" Value="{StaticResource somePicture2}" />
</DataTrigger>
</Style.Triggers>
</Style>
</Controls:NotificationBarControl.Style>
</Controls:NotificationBarControl>

DataBinding ListView creating components if a certain condition is met

I am working on a C# WPF project and I have a list view which uses data binding and a collection view.
Below is how my ListView is currently populated.
<ListView HorizontalAlignment="Left" Margin="547,12,0,41" Name="lstCallLogInformation" Width="320">
<ListView.Style>
<Style TargetType="ListView">
<Style.Triggers>
<Trigger Property="HasItems" Value="False">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListView">
<TextBlock Text="No items in your devices call log" FontWeight="Bold" HorizontalAlignment="Center" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</ListView.Style>
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<Expander IsExpanded="True">
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock FontWeight="Bold" Foreground="Gray" Text="{Binding Name}" VerticalAlignment="Bottom" />
</StackPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListView.GroupStyle>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Control.HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<DockPanel Style="{StaticResource onmouseover}">
<StackPanel HorizontalAlignment="Stretch" Orientation="Horizontal" Tag="{Binding logID}">
<Image Height="30" Source="{Binding base64ImageString, Converter={StaticResource ImageConverter}}" Width="30" />
<StackPanel Margin="10" Orientation="Vertical">
<TextBlock FontWeight="Bold" Text="{Binding contactNameOrPhoneNumber}" />
<StackPanel HorizontalAlignment="Stretch" Orientation="Horizontal">
<TextBlock HorizontalAlignment="Left" Text="{Binding dateString}" />
<TextBlock Margin="15, 0, 0, 0" Text="Duration: " />
<TextBlock HorizontalAlignment="Right" Text="{Binding humanReadableCallDuration}" />
</StackPanel>
</StackPanel>
<StackPanel Orientation="Vertical">
<Button Background="Transparent" BorderThickness="0" BorderBrush="Transparent" Tag="{Binding logID}" Name="btnAddAsContact" Click="btnAddAsContact_Click">
<Image Width="15" Height="15" Source="/BoardiesSMSServer;component/images/add.png" Tag="{Binding logID}" />
</Button>
<Button Background="Transparent" BorderThickness="0" BorderBrush="Transparent" Tag="{Binding logID}" Name="btnDeleteContactFromLog" Click="btnDeleteContactFromLog_Click">
<Image Width="15" Height="15" Source="/BoardiesSMSServer;component/images/delete.png" Margin="0,0,0,5" Tag="{Binding logID}" />
</Button>
</StackPanel>
</StackPanel>
</DockPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Within my DockPanel within the DataTemplate I have a StackPanel which contains various TextBlocks from the array that is passed into the binding.
In one of the StackPanels you will see you will see a TextBlock that binds to the element of contactNameOrPhoneNumber.
What I want to be able to do is if another value from the binding is null then I add a TextBlock for the contactNameOrPhoneNumber` element and alongside it have another TextBlock which contains another element of number``, if the other binding value is not null then this extra textblock of number is not added.
So in simple terms, what I want to be able to do is within the DataTemplate have a conditional if statement of if binding value == null add textblock to stackpanel else don't add textblock.
You can do that with Style + Triggers something like this:
<TextBlock>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding PropertyName}" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
Also you can create IValueConverter say ObjectToVisibilityConverter which will take input your property and if property is null return Visibility.Collapsed else return Visibility.Visible.
You can then bind property with Visibility value of TextBlock via Converter.
<TextBlock Visibility="{Binding PropertyName,
Converter={StaticResource ObjectToVisibilityConverter}}"/>

How to keep MVVM pattern with WPF Path object click?

On a WPF app, I got a Canvas object containing several Path object. I need to do things on click on those Path, things that would be in VM. There is no Command on Path, like Button for example. What is the best way to do that?
My solution so far is :
To create an MouseDown handler on the view code behind to catch my Path click
Send a message from here
Register to this message type on targeted VM in order to execute my business code.
It seems a bit overkill to me, what's your opinion? Do you see a more straitforward method?
Thanks a lot !
You can use Button with Path as Template:
<Button Command="{Binding MyCommand}">
<Button.Template>
<ControlTemplate TargetType="{x:Type Button}">
<Path ... />
</ControlTemplate>
</Button.Template>
</Button>
Just to add complementary infos about my last comment... The right way to position ItemTemplate is to style the ContentPresenter, within ItemsControl.ItemContainerStyle. Below you will find my finally working code :
<UserControl.Resources>
<Style x:Key="PathStyle" TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Path Data="{Binding Data}"
StrokeStartLineCap="Round"
Stretch="Fill"
StrokeEndLineCap="Round"
Stroke="{DynamicResource selectedZoneBorderColor}"
StrokeLineJoin="Round"
Width="{Binding Path=Width}"
Height="{Binding Path=Height}"
Fill="{DynamicResource selectedZoneColor}"
Panel.ZIndex="1"
StrokeThickness="3" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<ItemsControl Name="zonesContainer" ItemsSource="{Binding Zones}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Grid.Column="1" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button
Style="{StaticResource ResourceKey=PathStyle}"
Command="{Binding ElementName=zonesContainer,
Path=DataContext.ActivateZone}"
CommandParameter="{Binding Id}"
/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Path=Left}" />
<Setter Property="Canvas.Top" Value="{Binding Path=Top}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
All this with corresponding VM exposing a ObservableCollection, and a Zone class exposing all inside props (Width, Height, Top, Left...).
You can detailled answer here

Categories