How can you capture UWP pointer events on Popups/Tooltips/TeachingTip - c#

I'm using UWP's TeachingTip and I need to know when the pointer is over the control.
This is how I create and insert the TeachingTip:
var teachingTip = new TeachingTip
{
IsOpen = true,
Title = "hello",
Subtitle = "world"
};
var mainPage = (Window.Current.Content as Frame)?.Content as MainPage;
var content = (Windows.UI.Xaml.Controls.Canvas)mainPage.Content;
content.Children.Add(teachingTip);
I tried this but didn't get any event:
teachingTip.PointerEntered += TipPointerEntered;
teachingTip.PointerExited += TipPointerExited;
...
I also tried this but that didn't make any difference:
teachingTip.AddHandler(UIElement.PointerEnteredEvent, new PointerEventHandler(TipPointerEntered), true);
Then i read here this:
If you want to handle routed events from a Popup or ToolTip, place the handlers on specific UI elements > that are within the Popup or ToolTip and not the Popup or ToolTip elements themselves.
So my best guess is that a TeachingTip behaves the same. But I couldn't figure out how to gain access to the TeachingTip's children.
So I tried a different approach, maybe I could track the pointer once the tip is open and do some hit testing on the control. So I tried this:
var mainPage = (Window.Current.Content as Frame)?.Content as MainPage;
mainPage.AddHandler(UIElement.PointerMovedEvent, new PointerEventHandler(PointerMoved), true);
void PointerMoved(object sender, PointerRoutedEventArgs e)
{
var page = (Window.Current.Content as Frame)?.Content as MainPage;
var currPoint = e.GetCurrentPoint(page);
var elements = VisualTreeHelper.FindElementsInHostCoordinates(new Windows.Foundation.Point(currPoint.Position.X, currPoint.Position.Y), teachingTip);
foreach (UIElement element in elements)
{
// Eureka! found an element
}
}
but no eureka... if anyone can help...

Eventually I revisited this (a couple of years later...).
The solution is pretty simple, after applying a template you need to get the root element on which you want to register for pointer positions, like this:
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
var rootGrid = this.GetTemplateChild("ContentRootGrid") as Windows.UI.Xaml.Controls.Grid;
if (rootGrid != null)
{
rootGrid.PointerEntered += (sender, e) =>
{
SetPointerPosition(PointerPosition.Popup);
};
rootGrid.PointerExited += (sender, e) =>
{
SetPointerPosition(PointerPosition.Page);
};
}
}

Related

Xamarin - clearing ListView selection

I am actually working with this piece of code
using System;
using Xamarin.Forms;
using System.Diagnostics;
namespace CryptoUI
{
public class HomePage : Xamarin.Forms.MasterDetailPage
{
public HomePage()
{
// Set up the Master, i.e. the Menu
Label header = new Label
{
Text = "MENU",
Font = Font.SystemFontOfSize(20, FontAttributes.Bold),
HorizontalOptions = LayoutOptions.Center
};
// create an array of the Page names
string[] myPageNames = {
"Main",
"Page 2",
"Page 3",
};
// Create ListView for the Master page.
ListView listView = new ListView
{
ItemsSource = myPageNames,
};
// The Master page is actually the Menu page for us
this.Master = new ContentPage
{
Title = "Test",
Content = new StackLayout
{
Children =
{
header,
listView
},
}
};
// Define a selected handler for the ListView contained in the Master (ie Menu) Page.
listView.ItemSelected += (sender, args) =>
{
// Set the BindingContext of the detail page.
this.Detail.BindingContext = args.SelectedItem;
string currentPage = this.GetType().Name.ToString();
// This is where you would put your “go to one of the selected pages”
if(listView.SelectedItem.Equals("Main") && !currentPage.Equals("HomePage")){
AsyncPush(new HomePage());
}
else if(listView.SelectedItem.Equals("Page 2") && !currentPage.Equals("SecondPage")){
AsyncPush(new SecondPage());
}
else if(listView.SelectedItem.Equals("Page 3") && !currentPage.Equals("ThirdPage")){
AsyncPush(new ThirdPage());
}
// Show the detail page.
this.IsPresented = false;
};
listView.ItemSelected += (senders, e) => {
if (e.SelectedItem == null) return; // don't do anything if we just de-selected the row
// do something with e.SelectedItem
((ListView)senders).SelectedItem = null; // de-select the row
};
// Set up the Detail, i.e the Home or Main page.
Label myHomeHeader = new Label
{
Text = "Home Page",
HorizontalOptions = LayoutOptions.Center
};
string[] homePageItems = { "Alpha", "Beta", "Gamma" };
ListView myHomeView = new ListView {
ItemsSource = homePageItems,
};
var myHomePage = new ContentPage();
myHomePage.Content = new StackLayout
{
Children =
{
myHomeHeader,
myHomeView
} ,
};
this.Detail = myHomePage;
}
public async void AsyncPush(Page page)
{
await Navigation.PushAsync(page);
}
}
}
This code actually shows an easy FlyOut menu, using the Xamarin Forms technologies.
I am currently trying to understand how I could easily clear the ListView selection after I have selected which page I want to head to!
I found this piece of code on Xamarin's website for devs (http://developer.xamarin.com/guides/cross-platform/xamarin-forms/working-with/listview/);
listView.ItemSelected += (sender, e) => {
if (e.SelectedItem == null) return; // don't do anything if we just de-selected the row
// do something with e.SelectedItem
((ListView)sender).SelectedItem = null; // de-select the row
};
But I can't currently figure out how I should integrate it with my code above there :)
I would like to add to Jason's answer because it misses some vital information. When you set the ListView SelectedItem property to null, it will fire off the ItemSelected event again. So if you do not have a null check, it will throw an exception.
This is what it should look like:
void ItemSelected(object sender, EventArgs args)
{
if (((ListView)sender).SelectedItem == null)
return;
//Do stuff here with the SelectedItem ...
((ListView)sender).SelectedItem = null;
}
You're assigning the ItemSelected handler twice, which is a bad idea. All you should have to do is add this line to your existing ItemSelected handler
((ListView)sender).SelectedItem = null;
I had this same problem but the other solutions did not work for me. Since I needed to pass a custom object to the next page I nullified the selected item reference and used the item tapped reference for my custom object.
listView.ItemTapped += async (sender, e) =>{
await Navigation.PushAsync(new DetailPage(e.Item as CustomObject));
((ListView)sender).SelectedItem = null;
};
ListView.SelectedItem does not have setter (I mean simple Xamarin Android - not Xamarin.Forms). I suggest to use the following code:
private void DeselectEntities()
{
if (this.listView != null && this.listView.CheckedItemPositions != null)
{
this.listView.CheckedItemPositions.Clear();
}
}
I respect all given answers but in an MVVM app you'd better avoid too much code behind. What I usually do is following:
Bind ItemsSource of ListView as usual to an ObservableCollection where T is a CarViewModel in my case
Set SelectionMode="None": This does avoid the selection of SelectedItem on tap
Use EventToCommandBehavior (I use my own implementation; see github.com or use the one from Prism.Forms) to bind ItemTapped event of ListView to my ViewModel command SelectedCarChangedCommand.
In the ViewModel's SelectedCarChangedCommand you'll receive the tabbed item as ItemTappedEventArgs object.
<ListView
x:Name="CarsListView"
ItemsSource="{Binding Cars}"
SelectionMode="None">
<ListView.Behaviors>
<behaviors:EventToCommandBehavior
Command="{Binding SelectedCarChangedCommand}"
EventName="ItemTapped" />
</ListView.Behaviors>

Adding/Removing events based on context

I am new to C# and WinForms. I have some objects with the right click (context menu) events. However, depending on the context (for example depending on whether the user is in the wizard screen or the main screen), I want to alter what the right click does. I googled a bit and found that I can use += and -= operators but I still could not achieve what I want to do. Given the code below, for example,
Any ideas ?
EDIT: I want the OnClickCard behave differently in different places.
Sammple Code:
public override ContextMenuStrip GetContextMenuStrip(GoView view)
{
if (Selectable)
{
ContextMenuStrip contextMenu = new ContextMenuStrip();
if (!Empty)
{
// this is just for example so not showing the implementation
contextMenu.Items.Add(new ToolStripMenuItem(
"Delete",
null,
new EventHandler(OnClickDelete)));
}
// Empty
else
{
ToolStripMenuItem addCard = new ToolStripMenuItem("Add");
foreach (..some data..)
{
ToolStripMenuItem card = new ToolStripMenuItem(
data,
null,
new EventHandler(OnClickCard));
addCard.DropDownItems.Add(card);
}
}
}
else
{
return null;
}
}
private void OnClickCard(object sender, EventArgs e)
{
ToolStripMenuItem cardItem = (ToolStripMenuItem)sender;
if (cardItem.Text.Contains("ABC"))
{
Common.Forms.FormMMUSettings f = new FormMMUSettings(cardItem.Text,ParentMagazine.NextSite);
f.Show();
}
SetCard(new MagazineCard(2, cardItem.Text));
}
Are you saying you want to change the contents of the context menu depending on the circumstances when it's clicked? If so, the easiest way is to create multiple context menus, and just use an event to set whichever context menu you want.
ContextMenu menu1 = new ContextMenu();
MenuItem menu1Item1 = new MenuItem();
menu1Item1.Header = "Menu 1 Item 1";
menu1Item1.Click += new RoutedEventHandler(menu1Item1Clicked);
menu1.Items.Add(mnu1Item1);
MenuItem menu1Item2 = new MenuItem();
menu1Item2.Header = "Menu 1 Item 2";
menu1Item2.Click += new RoutedEventHandler(menu1Item2Clicked);
menu1.Items.Add(menu1Item2);
ContextMenu menu2 = new ContextMenu();
MenuItem menu2Item1 = new MenuItem();
menu2Item1.Header = "Menu 2 Item 1";
menu2Item1.Click += new RoutedEventHandler(menu2Item1Clicked);
menu2.Items.Add(menu2Item1);
MenuItem menu2Item2 = new MenuItem();
menu2Item2.Header = "Menu 2 Item 2";
menu2Item2.Click += new RoutedEventHandler(menu2Item2Clicked);
menu2.Items.Add(menu2Item2);
public void menu1Item1Clicked(object sender, EventArgs e)
{
}
etc..
Now you can just set whichever menu you need using:
myForm.ContextMenu = menu1;
Hope this helps.
+= and -= should work just fine but I'd suggest you to use a kind of handlers repo where you'd switch which handler to use. This should work like a strategy pattern where different wizard steps would be different strategies.
It would be easier to help you if you'll show us some code and point us which code parts do not work properly.
You seem to suggest that you would like to attach different handlers depending on context:
if(that)
obj.event += HandleThat;
else
obj.event += HandleSomethingElse;
That should work, but you can also do it in one handler:
obj.event += HandleAll;
void HandleAll(object sender, EventArgs arg) {
if(that)
HandleThat();
else
HandleSomethingElse();
}
EDIT: ok, your edited question really meant something else.
First obvious problem is that you add OnClickDelete as a handler, and show the implementation of OnClickCard which will not be called in your example. If it is a typo, then you just need to implement the handler method as you need. What part exactly is not working?

Visual Studio "Select Resource" dialog replacement

In my project I have more than 750 images in resource. Using VS built in "Select Resource" dialog is a nightmare to find and select one image - let's say - for a button in winforms designer.
It would be much more usable if it was some explorer like dialog and it is lack of search functionality.
Do you have any idea how to replace this dialog?
Is there any extension that can do that?
If there is no such extension I would create an extension/add-in whatever I need to do. Do you have any real experience if it can be done at all?
I thought I will find the appropriate dll and extend its beaviour, but unfortunately I cannot find which dll contains this tragedy
Any help would be appreciated, thank you!
The Resource Select dialog is a UITypeEditor. It is the internal class ResourceEditorSwitch<T> which internally uses the internal class ResourcePickerDialog and both of them are in Microsoft.VisualStudio.Windows.Forms.dll assembly which is one of Visual Studio's assemblies.
Since the implementation of the class is tightly coupled with some other internal classes of Visual Studio's assemblies, so it's hard to extract the class source code and customize it, but you having those information about the class will help us to take a look at its source code and let us to have more information about the class.
To customize the Resource Select dialogInstead you can get an instance of the class at design time, and before showing the dialog, manipulate the dialog using code to have a filtering feature like following gif, pay attention to the TextBox that I've added to the dialog:
You can filter the ListBox by typing in TextBox and using ↑ and ↓ keys, without changing the focus from TextBox you can select filtered results.
To do so, you should:
Create a ControlDesigner and register it as designer of your control. Then in its OnCreateHandle find the property which you are going to edit. For example BackgroundImage.
Find the UITypeEditor of that property. The editor is of type of ResourceEditorSwitch<T> which uses an instance of ResourcePickerDialog. Get the instance for ResourcePickerDialog.
Get the resourcePickerUI field and create an instance of ResourcePickerUI dialog. It is the dialog that you should change. The dialog contains some TableLayoutPanel. You should insert a TextBox in a suitable place and handle its TextChanged event and filter the values which is showing in the ListBox. All controls have names and you can simply access to them and change their properties and values.
After changing the form, assign it resourcePickerUI. This way, the editor will use the changed form and will show what you need.
Implementation
You can find the full working example in the following repository:
r-aghaei/CustomizeSelectResourceDialog
Download zip
Here is the code for the designer:
public class MyControlDesigner : ControlDesigner
{
protected override void OnCreateHandle()
{
base.OnCreateHandle();
var property = TypeDescriptor.GetProperties(this.Control)["BackgroundImage"];
var resourceEditorSwitch = property.GetEditor(typeof(UITypeEditor)) as UITypeEditor;
var editorToUseField = resourceEditorSwitch.GetType().GetProperty("EditorToUse",
BindingFlags.NonPublic | BindingFlags.Instance);
var editorToUse = editorToUseField.GetValue(resourceEditorSwitch);
var resourcePickerUIField = editorToUse.GetType().GetField("resourcePickerUI",
System.Reflection.BindingFlags.Instance |
System.Reflection.BindingFlags.NonPublic);
var resourcePickerUI = (Form)Activator.CreateInstance(resourcePickerUIField.FieldType);
ModifyForm(resourcePickerUI);
resourcePickerUIField.SetValue(editorToUse, resourcePickerUI);
}
void ModifyForm(Form f)
{
var resourceContextTableLayoutPanel = GetControl<TableLayoutPanel>(f, "resourceContextTableLayoutPanel");
var resourceList = GetControl<ListBox>(f, "resourceList");
resourceContextTableLayoutPanel.Controls.Remove(resourceList);
var tableLayoutPanel = new TableLayoutPanel();
tableLayoutPanel.Dock = DockStyle.Fill;
tableLayoutPanel.Margin = new Padding(0);
tableLayoutPanel.ColumnCount = 1;
tableLayoutPanel.RowCount = 2;
tableLayoutPanel.RowStyles.Add(new RowStyle(SizeType.AutoSize));
tableLayoutPanel.RowStyles.Add(new RowStyle(SizeType.Percent, 100));
List<string> list = new List<string>();
var textBox = new TextBox() { Dock = DockStyle.Fill, Margin = resourceList.Margin };
Action<string> applyFilter = (s) =>
{
if (string.IsNullOrEmpty(s))
{
resourceList.BeginUpdate();
resourceList.Items.Clear();
resourceList.Items.AddRange(list.ToArray());
resourceList.EndUpdate();
}
else
{
var list2 = list.Where(x => x.ToLower().StartsWith(s.ToLower())).ToList();
resourceList.BeginUpdate();
resourceList.Items.Clear();
resourceList.Items.Add("(none)");
resourceList.Items.AddRange(list2.ToArray());
resourceList.EndUpdate();
}
if (resourceList.Items.Count > 1)
resourceList.SelectedIndex = 1;
else
resourceList.SelectedIndex = 0;
};
var resxCombo = GetControl<ComboBox>(f, "resxCombo");
resxCombo.SelectedValueChanged += (s, e) =>
{
resxCombo.BeginInvoke(new Action(() =>
{
if (resourceList.Items.Count > 0)
{
list = resourceList.Items.Cast<string>().ToList();
textBox.Text = string.Empty;
}
}));
};
textBox.TextChanged += (s, e) => applyFilter(textBox.Text);
textBox.KeyDown += (s, e) =>
{
if (e.KeyCode == Keys.Up)
{
e.Handled = true;
if (resourceList.SelectedIndex >= 1)
resourceList.SelectedIndex--;
}
if (e.KeyCode == Keys.Down)
{
e.Handled = true;
if (resourceList.SelectedIndex < resourceList.Items.Count - 1)
resourceList.SelectedIndex++;
}
};
tableLayoutPanel.Controls.Add(textBox, 0, 0);
resourceList.EnabledChanged += (s, e) =>
{
textBox.Enabled = resourceList.Enabled;
};
tableLayoutPanel.Controls.Add(resourceList, 0, 1);
resourceContextTableLayoutPanel.Controls.Add(tableLayoutPanel, 0, 4);
}
T GetControl<T>(Control c, string name)
where T : Control
{
return (T)c.Controls.Find(name, true).FirstOrDefault();
}
}

TreeView Event keeps firing

I'm using the WPF version of TreeView (System.Windows.Controls.TreeView) and need to assign several events. As these are WPF most of the events are either different or behave in a different way hence my confusion as AfterExpand is not available
For now I need 2 events:
SelectedItemChanged which is at TreeView level
ExpandedEvent which is at TreeViewItem level
So far I have
private void DisplayGetEventTypes(UXEvent.GetEventTypesResp resp, CustomAsyncStateContainer state)
{
navBarControl.Groups.Clear();
if (resp.eventTypeItems != null)
{
UXEvent.EventType[] eventItems = resp.eventTypeItems;
int nodeCount = eventItems.Length;
for (int i = 0; i < nodeCount; i++)
{
UXEvent.TryEvent eventItem = new UXEvent.TryEvent();
eventItem.eventName = eventItems[i].name;
eventItem.eventId = eventItems[i].id;
NavBarGroup group1 = new NavBarGroup();
group1.Header = eventItems[i].name;
group1.Tag = eventItem;
group1.IsExpanded = false;
//Add dummy treeview to fill later if expanded
System.Windows.Controls.TreeView treeview = new System.Windows.Controls.TreeView();
treeview.SelectedItemChanged += new RoutedPropertyChangedEventHandler<object>(myTreeView_SelectedItemChanged);
AddHandler(TreeViewItem.ExpandedEvent, new RoutedEventHandler(myTreeView_ItemExpanded));
group1.Content = treeview;
group1.DisplaySource = DisplaySource.Content;
navBarControl.Groups.Add(group1);
}
}
}
and the following two draft event handlers for testing
void myTreeView_SelectedItemChanged(object sender,
RoutedPropertyChangedEventArgs<object> e)
{
// Set the data context of the text block to the selected value.
var node = (ItemsControl)e.OriginalSource;
System.Windows.Forms.MessageBox.Show("Selected!");
}
void myTreeView_ItemExpanded(object sender, RoutedEventArgs e)
{
// Set the data context of the text block to the selected value.
var node = (TreeViewItem)e.OriginalSource;
System.Windows.Forms.MessageBox.Show("Opening! - " + node.Header);
}
The problem I'm having is that myTreeView_ItemExpanded is firing multiple times when any treeviewItem is expanded. I think it fires multiple times due to the event bubbling .
Firstly, can anyone point me to some good resources where I can read up on this behaviour?
Secondly, how can I amend my code to make AddHandler(TreeViewItem.ExpandedEvent, new RoutedEventHandler(myTreeView_ItemExpanded)); a single event only?
Thank you
I spotted the problem. The line
AddHandler(TreeViewItem.ExpandedEvent, new RoutedEventHandler(myTreeView_ItemExpanded));
should read
treeview.AddHandler(TreeViewItem.ExpandedEvent, new RoutedEventHandler(myTreeView_ItemExpanded));
I assume the first line must have added the event handler to all available controls and all fired when the event triggered.

AutoScrollPosition Always Returns (0,0) For SplitPanel Control

I am trying to syncronize the scrolling of two splitcontainers within a splitpanel control. I have the code below:
Point mPrevPan1Pos = new Point();
Point mPrevPan2Pos = new Point();
void PanelPaint(object sender, System.Windows.Forms.PaintEventArgs e)
{
if (splitContainer1.Panel1.AutoScrollPosition != mPrevPan1Pos)
{
splitContainer1.Panel2.AutoScrollPosition = new System.Drawing.Point(-splitContainer1.Panel1.AutoScrollPosition.X, -splitContainer1.Panel1.AutoScrollPosition.Y);
mPrevPan1Pos = splitContainer1.Panel1.AutoScrollPosition;
}
else if (splitContainer1.Panel2.AutoScrollPosition != mPrevPan2Pos)
{
splitContainer1.Panel1.AutoScrollPosition = new System.Drawing.Point(-splitContainer1.Panel2.AutoScrollPosition.X, -splitContainer1.Panel2.AutoScrollPosition.Y);
mPrevPan2Pos = splitContainer1.Panel2.AutoScrollPosition;
}
}
However the AutoScrollPosition is always (0,0). I have AutoScroll enabled for both split containers. Why is this? What can I do to get the scroll position?
It looks like you copied the code from this answer: Scroll 2 panels at the same time
Did you wire up the events:
this.splitContainer1.Panel1.Paint += new PaintEventHandler(PanelPaint);
this.splitContainer1.Panel2.Paint += new PaintEventHandler(PanelPaint);

Categories

Resources