Bubbling events in WPF? Simple Question - c#

This is probably a really easy fix, but I am inexperienced in working with events so I am just going to ask it.
I have a ListBox with ListBoxItems in it. These ListBoxItems will be binded to a data source so they will change. I need a MouseDown event to be raised when a MouseDown action is performed on any of these ListBoxItem (because I am doing drag and drop). Since the values are changing, I cannot expect to wire the events together in the XAML like the following
<ListBox Name="ListBox1">
<ListBoxItem MouseDown="MouseDownEventName">Item A</ListBoxItem>
<ListBoxItem MouseDown="MouseDownEventName">Item B</ListBoxItem>
<ListBoxItem MouseDown="MouseDownEventName">Item C</ListBoxItem>
</ListBox>
This would be easy if I had static values, but since the values in the ListBox will change, I would prefer to write the following XAML
<ListBox Name="ListBox1" MouseDown="MouseDownEventName">
//Binded Values
</ListBox>
Then, when the ListBoxItem is selected, it would Bubble the event up to this MouseDownEventName, and I can grab ListBox1.SelectedItem at that time, the problem is, I am trying this right now, but it is not working. I have the following Code to handle the MouseDown, which is only rewriting label content at the moment to signify that the item has been MouseDown'ed.
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
private void ListBox_MouseDown(object sender, RoutedEventArgs e)
{
ListBox box = (ListBox)sender;
if (box != null && box.SelectedItem != null)
{
DragDrop.DoDragDrop(box, ItemListBox.SelectedItem, DragDropEffects.Move);
label1.Content = "MouseDown Event Fired";
}
}
}

Using XAML, you can provide a template for different types contained within the form. For example, in this case, you can specify that ListBoxItem's fire off a certain event handler. This is the bulk of the XAML markup code (details found here: How to catch Click on ListboxItem when the item is templated?)
<ListBox>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<EventSetter Event="PreviewMouseLeftButtonDown" Handler="ListBoxItem_PreviewMouseLeftButtonDown"/>
</Style>
</ListBox.ItemContainerStyle>
...
</ListBox>
Another thing to check before is to try and change your DragDrop.DoDragDrop() method call to something else to see if the issue has to do with that method. Since the Label's content is changing, I would imagine it has something to do with that method.

Related

Why are my custom controls not always receiving MouseEnter events?

Alright, I'm fairly new to WPF and I ran into a very strange problem. The relevant section of my XAML defines a Border around a ScrollViewer around a StackPanel which is populated using an ItemsControl that is then databound to a CollectionViewSource which in turn wraps a standard ObservableCollection. The ItemsControl defines a DataTemplate that contains only one tag: a custom control I've made called a StackElement. I'm handling three events from this control — MouseEnter, MouseLeave, and PreviewMouseLeftButtonUp. These events can fire, but do so unreliably.
For example, after some new StackElements are added, the MouseEnter event generally doesn't fire on the first StackElement until I've moused over a few others. Once a MouseOver manages to fire once, it continues to fire correctly on that StackElement from there on out.
However, the first time mousing over a StackElement doesn't always fail. If I approach the StackElements from beneath and try the last one first, it will always fire. When I do this, sometimes the first one will work, but the second one won't fire. Once, both of them did manage to operate correctly, but it happens infrequently.
I'm not multithreading anything, none of my parent controls handle events of their own, all event handlers consist only of a WriteLine() statement for debugging purposes, and the StackElement code-behind isn't handling any events either.
I've tried decoupling the ItemsControl from the CollectionViewSource in favor of binding it directly to the ObservableCollection, which did nothing other than (as I expected) bypass the sorting functionality I added to the ViewSource. I tried handling the events in the StackElement class itself, in addition to making them be tied to other controls contained within StackElement. I tried using DataTriggers, which if I remember worked as expected, but I need to include more advanced logic such as multiselection and the inability to lightly highlight an already-selected StackElement.
For context, I'm intending to use these events to lightly highlight StackElements when the user drags the mouse over them and to strongly highlight them when the mouse is pressed — basically, I need something that looks and feels like Windows File Explorer. From what I've seen this can't be accomplished in an elegant fashion with DataTriggers alone.
Here's my event handlers (in MainWindow.xaml):
private void StackElement_OnPreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
Console.WriteLine("OnPreviewMouseLeftButtonUp fired for a StackElement.");
}
private void StackElement_OnMouseEnter(object sender, MouseEventArgs e)
{
Console.WriteLine("OnMouseEnter fired for a StackElement.");
}
private void StackElement_OnMouseLeave(object sender, MouseEventArgs e)
{
Console.WriteLine("OnMouseLeave fired for a StackElement.");
}
Here's how I'm adding to the bound collection (for testing, which is why it's hooked up to a random button):
private void Btn_File_PreviewMouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
InitiativeStackElement t = new InitiativeStackElement(new Entity("TestName", 10, 11, 12, 13, null)); //InitiativeStackElement implements INotifyPropertyChanged so the databindings work
_entityProvider.Elements.Add(t); //_entityProvider is just a reference to a XAML-defined resource class, which is loaded up in the constructor so I don't have to call TryGetResource() whenever I want to use it. it's currently used for testing purposes only
}
Finally, here's the portion of my XAML containing the StackElements:
<Border Grid.Row="1"
Margin="0,1,0,0"
Style="{StaticResource StandardBorder}">
<ScrollViewer Name="Scv_InitiativeStack">
<StackPanel Name="Stp_InitiativeStack">
<ItemsControl Name="Its_InitiativeStack" ItemsSource="{Binding Source={StaticResource SortedInitiativeStack}}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<con:StackElement Element="{Binding}" PreviewMouseLeftButtonUp="StackElement_OnPreviewMouseLeftButtonUp" MouseEnter="StackElement_OnMouseEnter" MouseLeave="StackElement_OnMouseLeave"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ScrollViewer>
</Border>
The StackElement class just defines a single DependencyProperty of type InitiativeStackElement. The properties of this object are bound to a few controls within the StackElement, which always displays correctly. It's the behavior of the events that have me confused.
As described, I'm expecting the MouseEnter event to fire whenever the mouse is dragged onto the StackElement. However, it's only firing after I fulfill seemingly random conditions that shouldn't affect it's functionality, like mousing over another StackElement first. There are no error messages.
Alright, I was able to get the functionality I wanted using ListBox:
<Window.Resources>
<DataTemplate x:Key="InitiativeStackTemplate">
<con:StackElement Element="{Binding}"/>
</DataTemplate>
</Window.Resources>
<Border Margin="0,1,0,0"
Grid.Row="1"
Style="{StaticResource StandardBorder}">
<ScrollViewer Name="Scv_InitiativeStack">
<ListBox Name="Lbx_InitiativeStack"
SelectionMode="Extended"
ItemsSource="{Binding Source={StaticResource SortedInitiativeStack}}"
ItemTemplate="{StaticResource InitiativeStackTemplate}"
HorizontalContentAlignment="Stretch"/>
</ScrollViewer>
</Border>
Everything works as expected.

SelectionChanged fired also on nested controls?

Sorry for misleading title, I'll try to explain better.
I've a TabControl like this:
<dragablz:TabablzControl SelectionChanged="MainTabs_SelectionChanged" x:Name="MainTabs">
where inside I've different TabItems, I need to fire the event MainTabs_SelectionChanged each time the user change the TabItem, this working but the event is fired also when the selection of a combobox, available inside the tabitem, change.
This is the ComboBox:
<ComboBox Grid.Column="1" Grid.Row="1" ItemsSource="{Binding Groups}"
Margin="8,0,8,16" DisplayMemberPath="Name" SelectedItem="{Binding SelectedGroup}" />
why happen this?
why happen this?
Because SelectionChanged is a routed event.
Routed Events Overview: https://learn.microsoft.com/en-us/dotnet/framework/wpf/advanced/routed-events-overview
You could use the OriginalSource property to determine whether a tab was selected:
private void MainTabs_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.OriginalSource == MainTabs)
{
//do your thing
}
}

How to determine which child element of a ListView Item was clicked?

I'm developing a windows phone 8.1 app in XAML and C#. I have a ListView getting its Items from a bound list and displaying them through a DataTemplate. Now, in this DataTemplate there are multiple child elements, and when the user taps on an item in the list, I want to be able to determine what child element he actually touched. Depending on that, the app should either expand a view with more details inside the Item, or navigate to another page.
The ItemClick event handler of the ListView is ListView_ItemClick(object sender, ItemClickEventArgs e), and I thought e.OriginalSource would maybe give me the answer, but this just gave me the clicked ListItem.
I have yet to try if encapsulating the children with buttons and intercepting their click events would work, but I'm happy to try any alternative there might be for this.
I just found the solution myself. I set the ListView to SelectionMode="None" and IsItemClickEnabled="False", and then I added Tapped handlers for the individual child elements. Works just as I wanted.
I've got a TextBlock and an Image in one ListViewItem and have just used the Image_PointerPressed event. Doing that also fires the ItemClick event for the ListView so I disable it first, do the stuff I want, then re-enable the ItemClick event so that still fires when the TextBlock is pressed.
Code behind:
private async void imgDone_PointerPressed(object sender, PointerRoutedEventArgs e)
{
// disable click event so it won't fire as well
lvwCouncils.IsItemClickEnabled = false;
// do stuff
MessageDialog m = new MessageDialog("User Details");
await m.ShowAsync();
// Re-enable the click event
lvwCouncils.IsItemClickEnabled = true;
}
Xaml:
<ListView x:Name="lvwCouncils" ItemClick="lvwCouncils_ItemClicked" IsItemClickEnabled="true" >
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock
Grid.Column="1"
Text="{Binding council_name}"
FontSize="24"
Margin="10,10,30,10"
/>
<Border Height="20" Width="20" Margin="10,10,0,10" >
<Image x:Name="imgDone"
Source="Assets/user_earth.png" Stretch="UniformToFill" PointerPressed="imgDone_PointerPressed"/>
</Border>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Use the SelectionChanged event.
Cast the sender object to ListView type and then retrieve the item from the SelectedItem property.
Similar question here but for a different control :
Get the index of the selected item in longlistselector

Back-button gets focused on list manipulation

I am creating a ListView that can have some items removed from it (it's a list of favorites) by selecting them, bringing up the app bar and clicking "Remove from favorites". When the button is clicked, a method in the current view model is asked to remove this item from the list. After this happens, the UI gets updated, and the item is removed.
Now, I have two problems. The first one is that the back-button of the page receives the focus (it gets a dotted outline) when an item is removed, something which I do not want.
The second problem is that the list doesn't use the add / delete animation I've set it to use.
A solution to either of these would be appreciated.
Here is some pseudo code showing what happens:
XAML:
<GridView x:Name="FavoritesGridView"
Grid.Row="1"
SelectionMode="Multiple"
ItemTemplate="{StaticResource FavoritesOnSectionViewItemTemplate}"
ItemsSource="{Binding FavoritesList}"
ItemClick="ProgramGrid_OnItemClick"
IsItemClickEnabled="True"
SelectionChanged="FavoritesGridView_OnSelectionChanged"
ScrollViewer.HorizontalScrollMode="Disabled">
<GridView.ItemContainerStyle>
<Style TargetType="Control">
<Setter Property="Margin" Value="0,0,38,8"/>
</Style>
</GridView.ItemContainerStyle>
<GridView.ItemContainerTransitions>
<TransitionCollection>
<AddDeleteThemeTransition/>
</TransitionCollection>
</GridView.ItemContainerTransitions>
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<WrapGrid Orientation="Vertical" MaximumRowsOrColumns="9" />
</ItemsPanelTemplate>
</GridView.ItemsPanel>
</GridView>
Codebehind:
private void UnFavoriteButton_Click(object sender, RoutedEventArgs e)
{
viewModel.RemoveFromFavorites(FavoritesGridView.SelectedItems.Cast<FavoriteProgram>().AsEnumerable());
}
ViewModel:
public void RemoveFromFavorites(IEnumerable<FavoriteProgram> programs)
{
FavoriteController.RemoveFromFavorites(programs);
UpdateUi();
}
private void UpdateUi()
{
OnPropertyChanged("FavoritesList");
}
public IEnumerable<FavoriteProgram> FavoritesList
{
get { return CoreData.TvFavorites; } // A centralized list
}
FavoritesController:
public static void RemoveFromFavorites(IEnumerable<FavoriteProgram> programs)
{
if (programs.IsNullOrEmpty()) return;
foreach (var program in programs)
RemoveFromFavorites(program);
}
public static void RemoveFromFavorites(FavoriteProgram program)
{
if (!IsFavorite(program)) return;
var list = CoreData.TvFavorites.ToList();
list.Remove(program);
CoreData.TvFavorites = list.AsEnumerable();
}
Any ideas?
I see. So you have two problems.
[1]. The back button receives focus.
It is my opinion that the Back button should never receive focus. There is already a key gesture to go back, so setting focus is silly. Why it wasn't already disabled to have focus, I do not know. Here's all you do:
<Button TabIndex="-1" Style="{StaticResource BackButtonStyle}" />
Or you can do it with a style:
<Grid Background="Black">
<Grid.Resources>
<Style TargetType="Button" BasedOn="{StaticResource BackButtonStyle}" x:Name="MyBackButtonStyle">
<Setter Property="TabIndex" Value="-1" />
</Style>
</Grid.Resources>
<Button Style="{StaticResource MyBackButtonStyle}" />
</Grid>
Using this new style (or just updating the existing one) will result in the back button never receiving focus. If you want it to be able to receive focus, for some reason, then the solution would be to handle the GotFocus event and simply use (sender as Button).Focus(FocusState.Unfocused);. To be fair, you should also determine why you would be removing focus.
[2]. The animations are not happening
This is a common problem. The reason is, you do not want to setup animations on the ListView, you want to set up the animations on the ListView ItemsPanel. Here's all you want to do:
<ListView>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel>
<StackPanel.ChildrenTransitions>
<AddDeleteThemeTransition />
</StackPanel.ChildrenTransitions>
</StackPanel>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
It's as simple as that. (my sample is a StackPanel, remember to use a WrapGrid as you have in your code) You just had the transitions in the wrong place. So, now you can handle the focus problem you are having and you can get the transitions you want.
I might offer a bit of advice. Since you are using view models, it sounds strange to hear that you are not also using delegate commands. If you want to use MVVM at its best, delegate commands solve a lot of problems for you. Read here: http://blog.jerrynixon.com/2012/08/most-people-are-doing-mvvm-all-wrong.html
And for a second bit of advice. It sounds to me like you might be using the default templates from Visual Studio. Most developers start there. The problem is that those templates are not very great to teach best practices. My suggestion: don't be afraid of the blank template.
Best of luck!
Now, I have two problems. The first one is that the back-button of the
page receives the focus (it gets a dotted outline) when an item is
removed, something which I do not want.
This problem could be solved, by adding a TwoWay-Binding to the SelectedItem property of the GridView. After removing the favorite programms set the SelectedItem per code, so that it gets focused in the GridView.
XAML:
<GridView x:Name="FavoritesGridView"
SelectedItem="{Biding SelectedFavorite, Mode=TwoWay}" />
C#:
private FavoriteProgram _selectedFavorite;
public FavoriteProgram SelectedFavorite {
get {
return _selectedFavorite;
}
set {
_selectedFavorite = value;
OnPropertyChanged("SelectedFavorite");
}
}
After removing your items, set the property SelectedFavorite to an item in your FavoritesList.
public void RemoveFromFavorites(IEnumerable<FavoriteProgram> programs) {
FavoriteController.RemoveFromFavorites(programs);
UpdateUi();
SelectedItem = FavoritesList.FirstOrDefault(); // selects the first element in list.
}
The second problem is that the list doesn't use the add / delete
animation I've set it to use.
The problem here is that you always use a new collection/list for your property CoreData.TvFavorites after you have removed your favorites and therefore can the GridView not determine which items have been removed or added.
For binding scenarios there is a specialized collection named ObservableCollection<T>, that implements the interface INotifyCollectionChanged. The interface defines an event to notify (UI Elements) that items are added or removed from the collection. You should change your property FavoritesList to type ObservableCollection<FavoriteProgramm> and update the collection in your UpdateUI method to remove the relevant favorites.
private void UpdateUi()
{
//Update your FavoritesList to enable animations.
OnPropertyChanged("FavoritesList");
}
private ObservableCollection<FavoriteProgram> _favorites;
public ObservableCollection<FavoriteProgram> FavoritesList
{
get {
if (_favorites == null) {
_favorites = new ObservableCollection<FavoriteProgram>();
}
return _favorites;
}
}

In Silverlight, how to bind ListBox item selection to a Navigate event?

I am writing a windows-phone 7 application. I've got a page with a list of TextBlock(s) contained in a ListBox. The behavior I want is that upon clicking one of those TextBlock(s) the page is redirected to a different one, passing the Text of that TextBlock as an argument.
This is the xaml code: (here I am binding to a collection of strings, and the event MouseLeftButtonDown is attached to each TextBlock).
<ListBox x:Name="List1" ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock MouseLeftButtonDown="List1_MouseLeftButtonDown" Text="{Binding}"
FontSize="20"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
But this has been unsuccessful for me. I have tried attaching MouseLeftButtonDown event to either the individual TextBox(es) or to the ListBox. And I have had exceptions raised as soon as I use NavigationService.Navigate(uri). Which event should be attached? Should the event be attached to the individual items or to the list as a whole?
I have found a way to work around this problem by populating ListBox with HyperlinkButton(s). However, I would like to understand why the TextBox approach did not work.
This is my first attempt with Silverlight, so I might be missing something basic here.
There are a few ways to do this but I'll walk you through one of the the simplest (but not the purest from an architectural perspective).
Basically you want to find out when the selection of the ListBox changes. The ListBox raises a SelectionChanged event which can be listened to in the code behind.
<ListBox x:Name="List1" ItemsSource="{Binding}" SelectionChanged="SelectionChangedHandler" SelectionMode="Single" >
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" FontSize="20"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Then have a handler something like:
private void SelectionChangedHandler(object sender, SelectionChangedEventArgs e)
{
IList selectedItems = e.AddedItems;
string val = selectedItems.OfType<string>().FirstOrDefault();
NavigationService.Navigate(new Uri(val));
}
One thing you'll need to be aware of is that ListBoxes support multiple selection. For this reason, the event arguments give you back a list of the selected items. For simplicity, all I've done is taken the first value from this list and used that as the navigation value. Notice how I've also set the SlectionMode property of the ListBox to Single which will ensure the user can only select one item.
If I were doing this for real I'd look into creating an TriggerAction tat can be hooked up to an event trigger through xaml which will remove the for code behinds. Take a look at this link if you're interesetd.
In addition to Chris' and James' replies, I'd add that you will also need to clear the listbox selection in the event handler, otherwise the user won't be able to tap the same item twice on the listbox (because the item will already be selected).
Using James' approach, I would change the SelectionChangedHandler() implementation as follows:
private void SelectionChangedHandler(object sender, SelectionChangedEventArgs e)
{
// Avoid entering an infinite loop
if (e.AddedItems.Count == 0)
{
return;
}
IList selectedItems = e.AddedItems;
string val = selectedItems.OfType<string>().FirstOrDefault();
NavigationService.Navigate(new Uri(val));
// Clear the listbox selection
((ListBox)sender).SelectedItem = null;
}
What I would recommend is binding the SelectedItem property of the ListBox to a property in your ViewModel. Then, on the ListBox's SelectedItemChanged event, navigate to to the appropriate URL passing the data key on the QueryString, or upgrade to something like MVVM Light and put the actual SelectedItem object on the message bus for the child window to pick up. I have a sample of this second method on my Skydrive that you can check out.
HTH!
Chris

Categories

Resources