WPF: Get the item index touched in a stack panel - c#

I have a StackPanel. The contents of the StackPanel are defined in a Data Template and they are basically another StackPanel composed of two Buttons and two Text Blocks. Now... when I touch the StackPanel I can get the element I touched through
e.TouchDevice.DirectlyOver
Where e is a TouchEventArgs variable. But I can only get the actual UIElement... I'm intrested in getting the index of the element of the StackPanel that I touched.
Anyone knows how to do that?
Thank you.
XAML:
<DataTemplate x:Key="AddBookListTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="9.9*"/>
<ColumnDefinition Width="0.1*"/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Vertical" >
<StackPanel Orientation="Horizontal">
<Button blabla>
</Button>
<Button blabla>
</Button>
</StackPanel>
<TextBlock/>
<TextBlock/>
</StackPanel>
</Grid>
</DataTemplate>
And is reused
<ScrollViewer>
<ItemsControl ItemsSource="{Binding Books}" Margin="0 10 0 0" Background="Transparent" ItemTemplate="{StaticResource AddBookListTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" TouchEnter="StackPanel_TouchEnter"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ScrollViewer>

You could simply use the IndexOf method on the panel's Children collection:
private void panel_Touch(object sender, RoutedEventArgs e)
{
MessageBox.Show("You've touched n°" + panel.Children.IndexOf(sender as UIElement));
}

You could use VisualTreeHelper to walk up the visual tree, and then use IndexOf() to get the index:
private void panel_Touch(object sender, RoutedEventArgs e)
{
int index;
var button = sender as Button;
if (button != null)
{
var contentPresenter = VisualTreeHelper.GetParent(button) as ContentPresenter;
if (contentPresenter != null)
{
var stackPanel = VisualTreeHelper.GetParent(contentPresenter) as StackPanel;
if (stackPanel != null)
index = stackPanel.Children.IndexOf(contentPresenter);
}
}
}
Use a tool like Snoop to view the visual treee so you know what types to expect (I think that StackPanel puts ContentPresenters around each item, but I could be wrong).
It's a bit of a hack though. I'd recommend something more like this:
<StackPanel Orientation="Horizontal">
<Button Content="OK" Command="{Binding OKCommand}" />
<Button Content="Cancel" Command="{Binding CancelCommand}" />
</StackPanel>
Where OKCommand and CancelCommand are RelayCommands on your ViewModel.

Related

Wpf listview where listviewitem is a custom button

I have a problem. I have a list view in my xaml:
<ListView
Margin="0 10 0 0"
DockPanel.Dock="Top"
ItemsSource="{Binding Items}"
ScrollViewer.VerticalScrollBarVisibility="Hidden"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
BorderThickness="0"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate>
<local:ItemView/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
My item view looks like this (which is in UserControl tag):
<buttons:MyButton
cal:Message.Attach="DoSomething">
<buttons:MyButton.Visual>
<DockPanel
HorizontalAlignment="Left">
<local:Image
DockPanel.Dock="Left"
CountryCode="{Binding Source}"/>
<TextBlock
DockPanel.Dock="Left"
VerticalAlignment="Center"
Padding="15 0 0 0"
FontFamily="{StaticResource MediumFont}"
FontSize="16"
Foreground="{StaticResource DarkText}"
Text="{Binding TextValue}" />
</DockPanel>
</buttons:MyButton.Visual>
</buttons:MyButton>
Now the problem is that when i click on the list item, my selectedItem is not set. It always null unless I click on the very edge of the list item, then it sets the selected value, but i have to click again in the middle of the item to call the action I want.
My question is: How can I make my list view item on click (on the whole item plot) set the selected item and call the action I want?
P.S. I am using Caliburn Micro if this helps...
If you don't mind using code behind or a view model, you could do something like this:
<ListView.ItemTemplate>
<DataTemplate>
<Button BorderBrush="Transparent" Background="Transparent" Focusable="False">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding DataContext.ClickCommand, ElementName=ListViewName}" CommandParameter="{Binding}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<!-- YOUR TEMPLATE HERE -->
<Button.Template>
</DataTemplate>
</ListView.ItemTemplate>
This is a view/control related thing. You could handle the Click event for the Button and explicitly set the IsSelected property of the parent ListViewItem container like this:
private void OnClick(object sender, RoutedEventArgs e)
{
var listViewItem = FindParent<ListViewItem>(sender as DependencyObject);
if (listViewItem != null)
listViewItem.IsSelected;
}
private static T FindParent<T>(DependencyObject dependencyObject) where T : DependencyObject
{
var parent = VisualTreeHelper.GetParent(dependencyObject);
if (parent == null) return null;
var parentT = parent as T;
return parentT ?? FindParent<T>(parent);
}
XAML:
<ListView.ItemTemplate>
<DataTemplate>
<local:ItemView ButtonBase.Click="OnClick"/>
</DataTemplate>
</ListView.ItemTemplate>
Or you could set the SelectedItem property of the ListView to the DataContext of the element:
private void OnClick(object sender, RoutedEventArgs e)
{
FrameworkElement fe = sender as FrameworkElement;
if (fe != null)
listView.SelectedItem = fe.DataContext;
}
XAML:
<ListView x:Name="listView" ... />
I know this is an old question, but what I think you were looking for is something like this
<ListView ItemsSource="{Binding ListViewItemTemplates}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<Button x:Name="StartPage">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cal:Action.Target>
<cal:ActionMessage MethodName="StartPage">
<cal:Parameter Value="{Binding}" />
</cal:ActionMessage>
</cal:Action.Target>
</i:EventTrigger>
</i:Interaction.Triggers>
<Button.Template>
<ControlTemplate>
<StackPanel Width="210" Orientation="Horizontal">
<Image Source="{Binding Path=Source}"
Stretch="None" />
<TextBlock Text="{Binding Path=Name}" />
</StackPanel>
</ControlTemplate>
</Button.Template>
</Button>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
<ListView/>
Then you can easily bind this with the following
private ObservableCollection<ListViewItemTemplate> listViewItemTemplates;
public ObservableCollection<ListViewItemTemplate> ListViewItemTemplates
{
get => listViewItemTemplates;
set
{
listViewItemTemplates = value;
this.OnPropertyChanged("ListViewItemTemplates");
}
}
Where ListviewItemTemplate is a class consisting of two strings Name and Source

ListBox Selected Items are reset each time I switch tabs in may TabControl

I have the following TabControl:
<TabControl Name="tabControl" Grid.Row="0" MinWidth="270" HorizontalAlignment="Stretch" ItemsSource="{Binding Counters}" ContentTemplate="{StaticResource templateForTheContent}"
ItemTemplate="{StaticResource templateForTheHeader}">
</TabControl>
It uses this DataTemplate:
<Window.Resources>
<DataTemplate x:Key="templateForTheContent" >
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListBox Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="0"
ItemsSource="{Binding}"
SelectionMode="Multiple"
BorderThickness="1" BorderBrush="#FF8B8B8B" SelectionChanged="ListBox_SelectionChanged_1">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<Run Text="{Binding CounterName, Mode=OneWay}" />
<Run Text="{Binding InstanceName, Mode=OneWay}" />
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Name="RAMSelectAllButton" Margin="0,10,0,0" Grid.Column="0" Grid.Row="1">
<TextBlock Text="SELECT ALL"/>
</Button>
<Button Name="RAMUnSelectAllButton" Margin="0,10,0,0" Grid.Column="1" Grid.Row="1">
<TextBlock Text="UNSELECT ALL"/>
</Button>
</Grid>
</DataTemplate>
<DataTemplate x:Key="templateForTheHeader" >
<TextBlock Text="{Binding CategoryName}"/>
</DataTemplate>
</Window.Resources>
It works as expected, binding works well, everything would be totally fine if this issue wasn't present:
Each time I switch a tab in my TabControl, selected items of a ListBox in my previous tab are reset - so when I go back to that tab - nothing is selected.
How to fix this?
//EDIT
here's my ListBox_SelectionChanged_1 method:
private void ListBox_SelectionChanged_1(object sender, SelectionChangedEventArgs e)
{
System.Windows.Controls.ListBox listBoxTemp = sender as System.Windows.Controls.ListBox;
PerformanceCounter counterTemp = (PerformanceCounter)listBoxTemp.Items[0];
if (!appData.SelectedCounters.ContainsKey(counterTemp.CategoryName))
appData.SelectedCounters.Add(counterTemp.CategoryName, new List<PerformanceCounter>());
appData.SelectedCounters[counterTemp.CategoryName].Clear();
foreach (PerformanceCounter counter in listBoxTemp.SelectedItems)
{
appData.SelectedCounters[counterTemp.CategoryName].Add(counter);
}
}
ListBox is bound to Counters, which is Observable Collection:
public ObservableCollection<ObservableCollection<PerformanceCounter>> Counters
{
get { return _Counters; }
}
ObservableCollection<ObservableCollection<PerformanceCounter>> _Counters = new ObservableCollection<ObservableCollection<PerformanceCounter>>();
(i am not familiar with TabControls, or WPF for that matter, but i would suggest a solution like this:)
Backup the selected items of your ListBox in a Dictionary.
var selectionBackups = new Dictionary<ListBox, IEnumerable<ListBoxItem>>();
I'd keep this field updated in ListBox_SelectionChanged_1(). Whenever you enter a Tab, overwrite the concerned ListBox's selection.
void TabEnter(object sender, TabEventArgs e)
{
ListBox lb = ... //acquire the current Listbox
OverwriteSelection(lb, selectionBackups[lb]); //set the ListBox's selection
}
(You might want to prevent the ListBox_SelectionChanged_1() from triggering when you restore the selection. That could be done like this:
bool auto_select = false; //set this to true while editing selections
private void ListBox_SelectionChanged_1(object sender, SelectionChangedEventArgs e)
{
if(auto_select)
return;
...
}
)

WPF ItemsControl elements - Resize content based on the window size (Anchoring)

im working with wpf and i have the problem that i want to bind a collection to an element which resize its content based on the window size.
To make it more clear a little example:
With static behaviour i would do something like that.
<Grid Margin="10,10,10,10">
<Grid.RowDefinitions>
<RowDefinition Height="50*"/>
<RowDefinition Height="50*"/>
<RowDefinition Height="50*"/>
<RowDefinition Height="50*"/>
</Grid.RowDefinitions>
<Button Grid.Row="0"></Button>
<Button Grid.Row="1"></Button>
<Button Grid.Row="2"></Button>
<Button Grid.Row="3"></Button>
</Grid>
In that case all the Buttons would grow/shrink with the window.
But now i want to have it more dynamic.
I have an ObservableCollection which contains all the elements to be added (dynamic amount).
For the first implementation ive added all the elements to a StackPanel. But the controls within the StackPanel dosent resize so that i think about using a grid instead.
Actual solution:
<Window.Resources>
<DataTemplate DataType="{x:Type local:OwnObject}">
<Button DataContext="{Binding}" Content="{Binding Text}" Margin="0,0,0,0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
</DataTemplate>
</Window.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding SubItems}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel x:Name="stackPanel" Margin="0,0,0,0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
How is it possible to use the ItemsControl to generate one row for each element and add it to that row? Other solutions to deal tith the problem are also welcome.
You can use UniformGrid as ItemsPanelTemplate cause it's a grid where all the cells in the grid have the same size. So the code will look like that.
<ItemsControl Name="icTest" VerticalContentAlignment="Stretch">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:OwnObject}">
<DockPanel Margin="0">
<Button Content="{Binding Text}" Margin="0,0,0,0"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
</DockPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="1" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
In code behind it's looks like that.
public class OwnObject : INotifyPropertyChanged
{
private string _text;
public string Text
{
get { return _text; }
set { _text = value; NotifyPropertyChanged( "Text" ); }
}
...
}
...
ObservableCollection<OwnObject> objects = new ObservableCollection<OwnObject>();
objects.Add( new OwnObject() { Text = "first" } );
objects.Add( new OwnObject() { Text = "second" } );
objects.Add( new OwnObject() { Text = "third" } );
icTest.ItemsSource = objects;

how to get click event in itemTemplate

I have a gridView.
ıt has an Itemptemplate (textblock, image) and itemsource. textBlock and Image are binded to itemsource.
I want to add a button to ItempTemplate but I couldn't able to detect the eventHandler.
In my .cs file I dont see textblock, image or button.
how can I set the event,
here is the code of item template
<DataTemplate x:Key="IDViewStyle">
<Grid Width="350" Height="450" >
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="40"/>
</Grid.RowDefinitions>
<Grid>
<Border Background="#B2060606" />
<Button HorizontalAlignment="Right" BorderThickness="0" x:Name="eraseButton" VerticalAlignment="Top">
<Image Source="/Assets/Images/erease.png" Width="90" Margin="0,-7,-15,15"/>
</Button>
<StackPanel Margin="0" Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock HorizontalAlignment="Center" TextWrapping="Wrap" Text="+" VerticalAlignment="Center" Style="{StaticResource PageHeaderTextStyle}" FontSize="160" Margin="0"/>
<TextBlock TextWrapping="Wrap" Text="Ekle" TextAlignment="Center" Style="{StaticResource HeaderTextStyle}"/>
</StackPanel>
<Image Stretch="Fill" Source="{Binding Image}"/>
</Grid>
<TextBlock TextWrapping="Wrap" Text="{Binding Type}" VerticalAlignment="Top" Grid.Row="1" Style="{StaticResource SubheaderTextStyle}" TextAlignment="Center"/>
</Grid>
</DataTemplate>
and my .cs file
Data.IdentityTypeCollection collection;
gView.SelectionChanged += lvIdTypes_SelectionChanged;
collection = new Data.IdentityTypeCollection();
gView.ItemsSource = collection;
gView.ScrollIntoView(collection);
and my mainpage.xaml
<GridView x:Name="gView" Grid.Row="1" Grid.RowSpan="2" Margin="117,0,0,100" ItemTemplate="{StaticResource IDViewStyle}"/>
how can I use button event in the item template
On GridView you can assign a property ItemClick="YourClickEvent" like:
<GridView
x:Name="itemGridView"
IsItemClickEnabled="True"
ItemClick="ItemView_ItemClick"></GridView>
and in your .cs file the event handler:
void YourClickEvent(object sender, ItemClickEventArgs e)
{
//your codes here
}
Make sure GridView IsItemClickEnabled is set to True All set!
If I understand your question then this is what I did after using the debugger to look around. This is a UWP application. The Button is in a Grid in a DataTemplate in a "MedView" GridView. The following sets the ItemsSource:
List<ViewItemClass> lvil = new List<ViewItemClass>();
// create lvil
MedView.ItemsSource = lvil;
Then I added a Click event handler for the Button and the following is the code for that:
Button b = sender as Button;
if (b == null)
return;
Grid g = b.Parent as Grid;
if (g == null)
return;
ViewItemClass lvi = g.DataContext as ViewItemClass;
if (lvi == null)
return;
// use the item

How to remove a custom item from a listBox and ObservableCollection when click on a button

I have the listBox and a ObservableCollection. The listBox.ItemSource (listNotify.ItemSource) is set to that ObservableCollection (errosList).
The problem that i have is that i don`t know how to remove the correct element from errorsList when the user click on the button with content x from listBox. For the item of the listBox i use a ItemTemplate, inside a stackPanel and in stackPanel i have a button.
Bellow is the XAML code:
<ListBox x:Name="listNotify">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="35">
<Image Height="16" Source="/Template;component/Resources/error.png" Stretch="Fill" VerticalAlignment="Top" Width="16"/>
<StackPanel Orientation="Vertical">
<HyperlinkButton Content="{Binding ErrorHeader}" HorizontalAlignment="Left" Height="16" Width="125"/>
<TextBlock Text="{Binding ErrorMessage}" HorizontalAlignment="Left" Width="405" d:LayoutOverrides="VerticalAlignment" />
</StackPanel>
<Button Content="x" Width="20" Height="20" Click="removeError_Click"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The code is from a silverlight 4 project.
Thanks.
private void removeError_Click(object sender, RoutedEventArgs e) {
FrameworkElement fe = sender as FrameworkElement;
if (null != fe) {
_observableCollection.Remove((YourType)fe.DataContext);
}
}
Should do what your looking for. Replace YourType with the type you declared in the ObservableCollectiion.
Don't you have an ID-like property in your Items of the errorsList-Collection? Then you could use the Tag-Property of the Button to archieve this:
<Button Content="x" Width="20" Height="20" Tag="{Binding ID}" Click="Button_Click" />
and in the click-event of the button:
string id = ((Button) sender).Tag.ToString();
var itemToRemove = errorsList.Where(x => x.ID == id).First();
errorsList.Remove(itemToRemove);
Hope that helps

Categories

Resources