Why are my tunneling event arguments object and bubbling event arguments object not equal? - routedevent

I'm working through the 70-511 book, and am looking at the section on Routed Events.
I noticed it mentions that bubbling-tunneling event pairs share the same EventArgs instance, so if you handle the tunneling event (eg PreviewMouseDown), it halts the paired bubbling event (eg MouseDown); I've tried this and it works... But, if I test for equality each time the event handler fires (for test purposes I'm using 1 event handler for both events) it seems as though the EventArgs are NOT the same instance (ie they have different hashvalues and Object.Equals returns false)...
It would greatly improve my understanding of how routed events work if I could figure out why this is!
Any .NET gurus our there care to explain?
I've checked out the Pro WPF book (excellent book) and this also just states:
"To make life more interesting, if you mark the tunneling event as handled, the bubbling event won’t occur. That’s because the two events share the same instance of the RoutedEventArgs class."
If the two events share the SAME INSTANCE of a class, shouldn't the eventargs have the same hashvalues and return 'True' for Object.Equals???
private RoutedEventArgs args;
private void MouseDownHandler(object sender, MouseButtonEventArgs e)
{
listEvents.Items.Add(string.Format("{0} - {1} - {2} - {3}",
sender.GetType().Name, e.RoutedEvent.ToString(), e.Source.GetType().Name,
e.OriginalSource.GetType().Name));
listEvents.Items.Add(e.GetHashCode().ToString());
if (args != null) listEvents.Items.Add(e.Equals(args).ToString());
args = e;
}
The XAML:
<Window x:Class="Chapter_2___WPF_RoutedEvents.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="428" Width="658"
PreviewMouseDown="MouseDownHandler" MouseDown="MouseDownHandler">
<Grid Name="grid"
MouseDown="MouseDownHandler" PreviewMouseDown="MouseDownHandler">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ListBox Name="listEvents" Grid.Column="1"/>
<Button Content="Click Me!" Width="150" Height="50" Margin="10" Grid.Column="0"
MouseDown="MouseDownHandler" PreviewMouseDown="MouseDownHandler"/>
</Grid>
</Window>

When I run your code and click the button, it does return the same hash code and 'True' for e.Equals(args). If I click again, e.Equals(args) returns 'False' because it is a new instance of RoutedEventArgs for each click, but the next one returns True because the tunneling event is the same as the bubbling event.

Related

Call ICommand outside item source

I have an ICommand in my PageViewModel and want it to be call in my CheckedChanged of RadioButton. However, this RadioButton is inside:
<views:RoundedPage>
<Carousel ItemSource="...">
<DataTemplate DataType="...">
<CollectionView ItemSource="...">
<DataTemplate DataType="...">
<RadioButton CheckedChanged="" />
</DataTemplate>
</CollectionView>
</DataTemplate>
</Carousel>
</views:RoundedPage>
So how am I gonna call this command outside those sources.
Thank you
At first, I don't recommend you to deal with the UI event in your view model. The view model is used to resolve the logic behavior behand the view.
So you can set the CheckedChanged event by the following method:
1.create a void method in the page.cs, such as
private void RadioButton_CheckedChanged(object sender, CheckedChangedEventArgs e)
{
//do something
}
2.binding in the xaml
<RadioButton CheckedChanged="RadioButton_CheckedChanged"/>
If you still want to deal with the event in the viewmodel, you need to use the behavior to covert the event to a command.
There is a simple in the official document and you can check it:
https://learn.microsoft.com/en-us/samples/xamarin/xamarin-forms-samples/behaviors-eventtocommandbehavior/

How to display two text boxes as pop up using DisplayPromptAsync method in xamarin?

When I use -
string result = await DisplayPromptAsync("Question 1", "What's your name?");
It shows only one textbox in the pop-up. But how to display two or more textboxes in the pop-up?
Any help would be greatly appreciated.
As IvanIčin said that you can use Rg.Plugins.Popup to create custom popup.
Firstly, install Rg.Plugins.Popup bu nuget package..., then creating popup page
<pages:PopupPage
x:Class="FormsSample.popup.popup2"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:pages="clr-namespace:Rg.Plugins.Popup.Pages;assembly=Rg.Plugins.Popup">
<pages:PopupPage.Content>
<StackLayout
Padding="20,0"
BackgroundColor="CadetBlue"
HorizontalOptions="FillAndExpand"
VerticalOptions="Center">
<Label Text="Question 1" />
<Label Text="this is one question!" />
<Entry />
<Entry />
<Button
x:Name="btnsub"
Clicked="btnsub_Clicked"
Text="subit" />
</StackLayout>
</pages:PopupPage.Content>
</pages:PopupPage>
public partial class popup2 : PopupPage
{
public popup2()
{
InitializeComponent();
}
private void btnsub_Clicked(object sender, EventArgs e)
{
}
}
To call this Popup Page from contentpage button.click event.
private async void btnPopupButton_Clicked(object sender, EventArgs e)
{
await PopupNavigation.Instance.PushAsync(new popup2());
}
You can see the screenshot:
You can't as it is not intended to. You can create a custom pop-up either by using some pop-up plug-in or by creating your custom code based on the native prompts (similar to what Xamarin.Forms do).
Just for the record having one input field is very generous from Xamarin as the native Android or iOS developers don't have such a prompt with the input field out of the box (though it isn't too hard to create it but still it goes much beyond one line of code).

Is it possible to implement 5-click command? Or Long Press on Image

<Image HorizontalOptions="Center" WidthRequest="150" Source="AppLogoWhite.png">
<Image.GestureRecognizers>
<TapGestureRecognizer
Tapped="OnTapGestureRecognizerTapped"
NumberOfTapsRequired="2" />
</Image.GestureRecognizers>
</Image>
It works as expected. However when I change NumberOfTapsRequired from 2 to 5, it doesn't work any more. Is this behaviour expected? Is it possible to implement 5-click command?
Or Long Press on Image?
I thought there was an issue, or maybe intended, behaviour on Android that didn't allow you to set a value higher than 2.
Of course one way to get around that is to implement a mechanism in your tapped event that counts for you.
In your code-behind add a counter: private int _tapCounter = 0;
And with each tap increment it:
private void OnTapGestureRecognizerTapped(object sender, EventArguments EventArgs)
{
_tapCounter++;
if (_tapCounter == 5)
{
_tapCounter = 0;
DoStuff();
}
}
Of course a long press gesture is also an option but you would need to implement it yourself of use external libraries. For instance MR.Gestures

Equivalent of ItemAppearing in xamarin UWP

I have a ListView bind to list of BitmapImage.
I want to get the Index of current image in focus when I scroll thru this list.
But, I notice that ItemAppearing property is not there in UWP but it is there in Xamarin Forms.
How can I get the index of the current item in view?
Thanks!
<ScrollViewer Grid.Row="0" ZoomMode="{x:Bind ZoomMode, Mode=OneWay}" HorizontalScrollMode="Enabled" HorizontalScrollBarVisibility="Auto">
<ListView HorizontalAlignment="Center" ItemsSource="{x:Bind ImagePages, Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="BitmapImage">
<Image Source="{x:Bind }" Margin="0 2" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListView>
</ScrollViewer>
For starters, the ItemAppearing property is not the behavior you are looking for. The ItemAppearing event for the ListView in Xamarin Forms is fired when the list item is rendered. For a small list this event will be fired for all items immediately. The equivalent event in UWP is ListView.ChoosingItemContainer event which like the ItemAppearing event, unless the ListView is virtualized is fired for all items in the list. Even for a large virtualized list, it is fired for several pages of items.
This is not what you want. As I understand it, you want to know the image that is visible at the top of the list view when the list is scrolled. Here is how to do that.
First of all. Get rid of the ScrollViewer. The ListView already has a ScrollViewer inside of it.
<ListView x:Name="listViewImage" Grid.Row="0" HorizontalAlignment="Center" ItemsSource="{x:Bind ImagePages, Mode=OneWay}"
Loaded="listViewImage_Loaded">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="BitmapImage">
<Image Source="{x:Bind }" Margin="0 2"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListView>
Note that I have named the ListView and I have added a Loaded event handler. In this handler, find the ScrollViewer inside the ListView and attach a handler to the ViewChanged event.
private void listViewImage_Loaded(object sender, RoutedEventArgs e)
{
Border b = VisualTreeHelper.GetChild(listViewImage, 0) as Border;
ScrollViewer sv = b.Child as ScrollViewer;
sv.ViewChanged += Sv_ViewChanged;
}
In the view changed handler, find the first visible ListViewItem and get its index in the collection. This is what you want.
private void Sv_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
{
ScrollViewer sv = sender as ScrollViewer;
GeneralTransform gt = sv.TransformToVisual(this);
Point p = gt.TransformPoint(new Point(0, 0));
List<UIElement> list = new List<UIElement>(VisualTreeHelper.FindElementsInHostCoordinates(p, sv));
ListViewItem item = list.OfType<ListViewItem>().FirstOrDefault();
if(item != null)
{
int index = listViewImage.IndexFromContainer(item);
Debug.WriteLine("Visible item at top of list is " + index);
}
}

Updating SilverLight lists when Bound in-memory collection gets a new member

I'm trying to pupulate one of three listboxes from a (fourth) source list box. The source has a list of school Subjects which are classified as elementary, middle or high school subjects. The source listbox is a list of checkboxes. The user clicks on the checkbox and one of the other three are intended to get a copy of the Subject object from the source list. I've got the thing wired up and successfully hit a CheckBox_Changed method. I can successfully locate the Subject instance from the source list and add it to the target list's Source array.
What I can't do is show the update on the Silverlight control that the target array is bound to.
Any ideas?
Thanks
private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
var cb = (CheckBox)sender;
var children = ((Grid)cb.Parent).Children;
// cb has a sibling TextBlock item that has the index of the item in
// the list of subjects
var ch2 = children[1] as TextBlock;
var subjectIndexStr = ch2.Text;
var myWorkingSubject = workingSubjectList[int.Parse(subjectIndexStr)];
switch (myWorkingSubject.SubjectLevelId)
{
// updates to the elementarySubjects, middleSubjects and highSubjects
// don't get reflected in the lists that use them as a resource.
case (int)SubjectLevels.Elementary:
elementarySubjects.Add(myWorkingSubject);
break;
case (int)SubjectLevels.Middle:
middleSubjects.Add(myWorkingSubject);
break;
case (int)SubjectLevels.High:
highSubjects.Add(myWorkingSubject);
break;
default: break;
}
}
// this is how the target classes are declared.
public class SubjectsElementary : ObservableCollection<WorkingSubject>
{
}
public class SubjectsMiddle : ObservableCollection<WorkingSubject>
{
}
public class SubjectsHigh : ObservableCollection<WorkingSubject>
{
}
Here are snippets from the .xaml file
<TutorRouterSvc:WorkingSubjectList x:Key="subjects" />
<TutorRouterSvc:SubjectsElementary x:Key="elementarySubjects" />
<TutorRouterSvc:SubjectsMiddle x:Key="middleSubjects" />
<TutorRouterSvc:SubjectsHigh x:Key="highSubjects" />
<ListBox x:Name="subjectList" ItemsSource="{Binding Mode=OneWay, Source={StaticResource subjects}}">
<ListBox.Resources>
</ListBox.Resources>
<ListBox.ItemTemplate>
<StaticResource ResourceKey="DataSubjectsTemplate1"/>
</ListBox.ItemTemplate>
</ListBox>
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ListBox Margin="0,0,8,0" x:Name="elementarySubjectList"
ItemsSource="{Binding Mode=OneWay, Source={StaticResource elementarySubjects}}"
Background="#FFE75151" Grid.Row="0">
<ListBox.ItemTemplate>
<StaticResource ResourceKey="DataSubjectsTemplate1"/>
</ListBox.ItemTemplate>
</ListBox>
<ListBox Margin="0,0,8,0" x:Name="middleSubjectList"
ItemsSource="{Binding Mode=OneWay, Source={StaticResource middleSubjects}}"
Background="#FFE75151" Grid.Row="1">
<ListBox.ItemTemplate>
<StaticResource ResourceKey="DataSubjectsTemplate1"/>
</ListBox.ItemTemplate>
</ListBox>
<ListBox Margin="0,0,8,0" x:Name="highSubjectList"
ItemsSource="{Binding Mode=OneWay, Source={StaticResource highSubjects}}"
Background="#FFE75151" Grid.Row="1">
<ListBox.ItemTemplate>
<StaticResource ResourceKey="DataSubjectsTemplate1"/>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
I'm not quite sure, but this may be fixable by doing the changes inside a Dispatch.BeginInvoke().
You could refactor the switch statement to a new method called UpdateListBox, then call it:
Dispatcher.BeginInvoke(() => UpdateListBox(myWorkingSubject.SubjectLevelId))
Maybe this is happening because the XAML is Newing up a new instance of your objects, which it's databinding to.
Try adding this to the cosntructor on your Page.xaml.cs (or where ever the control is located);
_subjects = Resources["subjects"] as WorkingSubjectsList;
_elementarySubjects = Resources["elementarySubjects"] as SubjectsElementary;
etc...
Maybe that will help. I've implemented the same concept by binding listboxes to Observable collections on several occassions and haven't experienced what you're encountering.
I do have a couple of suggestions:
have you tried this on your check changed event?
workingsubject _item = workingSubjectList[subjectsList.selectedindex];
switch (_item.SubjectLevel) //I'm assuming this property as you have the ID and it looks to be an enumeration
{
case Elementary:
elementarySubjects.Add(_item):
break;
case Middle:
middleSubjects.Add(_item):
break;
case High:
highSubjects.Add(_item):
break;
case default:
throw new Exception("Unrecognized Subject Level");
}
hth.

Resources