I have been trying to implement a feature into my application where when a user hovers over a Grid item housed within a GridView for a couple seconds it will display a medium sized popup Grid with more details. I have tried a few methods I found online, but I have been running into errors that I am not understanding the root cause of, and I have the feeling there is a much simpler approach to add this feature than what I have found online so far.
First, I tried a solution I adapted by using a derivative class of Grid. I ran into some issues with that that (detailed a bit more in my last question) with the main issue being that my Timer no longer would trigger and items using my "HoverGrid" data template within the GridView template would no longer show Image as a child item of the HoverGrid. So, I abandoned that approach.
Then I tried to implement the Timer directly in my Page's code-behind, which seemed to work (partially) as it is properly triggering PointerEnter, PointerExit, and TimerElapsed events, however, when trying to manipulate anything UI related in the TimerElapsed event I would get:
System.Exception: 'The application called an interface that was marshalled for a different thread. (Exception from HRESULT: 0x8001010E (RPC_E_WRONG_THREAD))'
Here is the XAML:
<Page
x:Class="myproject.Pages.MyPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Height="950"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
mc:Ignorable="d">
<Grid x:Name="ContentGrid">
<ListView
x:Name="AreaPanels"
Margin="0,10,0,0"
HorizontalContentAlignment="Stretch"
SelectionMode="None">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<ItemsStackPanel AreStickyGroupHeadersEnabled="True" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<controls:AdaptiveGridView
CanDragItems="True"
DesiredWidth="140"
ItemHeight="140">
<controls:AdaptiveGridView.ItemTemplate>
<DataTemplate x:Name="IconTextTemplate" x:DataType="Image">
<Grid
PointerEntered="ImageHoverStart"
PointerExited="ImageHoverEnd">
<Image
Opacity="1"
Source="/Assets/Placeholders/sample_image.jpg"
Stretch="UniformToFill" />
</Grid>
</DataTemplate>
</controls:AdaptiveGridView.ItemTemplate>
<Grid />
<Grid />
<Grid />
<Grid />
</controls:AdaptiveGridView>
</ListView>
</Grid>
</Page>
Code-behind (C#):
using System.Diagnostics;
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Input;
namespace myproject.Pages
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MyPage : Page
{
//Variables for Grid functions
public object hoveredGridItem = null;
public PointerRoutedEventArgs hoveredGridItemArgs = null;
public System.Timers.Timer hoverTimer = new System.Timers.Timer();
public event System.Timers.ElapsedEventHandler TimerElapsed
{ add { hoverTimer.Elapsed = value; } remove { hoverTimer.Elapsed -= value; } }
public MyPage()
{
this.InitializeComponent();
hoverTimer.Enabled = true;
hoverTimer.Interval = 2000;
TimerElapsed = OnTimerElapsed;
}
private void ImageHoverStart(object sender, PointerRoutedEventArgs e)
{
Debug.WriteLine("Fired: ImageHoverStart");
hoverTimer.Start();
hoveredGridItem = sender;
hoveredGridItemArgs = e;
}
private void ImageHoverEnd(object sender, PointerRoutedEventArgs e)
{
Debug.WriteLine("Fired: ImageHoverEnd");
hoverTimer.Stop();
hoveredGridItem = null;
hoveredGridItemArgs = null;
}
private void OnTimerElapsed(object source, System.Timers.ElapsedEventArgs e)
{
Debug.WriteLine("Timer elapsed!");
hoverTimer.Stop();
if (hoveredGridItem.GetType().ToString() == "Windows.UI.Xaml.Controls.Grid")
{
Debug.WriteLine(hoveredGridItem.ToString());
Debug.WriteLine(hoveredGridItemArgs.ToString());
//Get the hovered image and associated arguments that were stored
Grid itm = (Grid)hoveredGridItem;
PointerRoutedEventArgs f = hoveredGridItemArgs;
//Get image position and bounds
//GeneralTransform transform = itm.TransformToVisual(Window.Current.Content);
//Point coordinatePointToWindow = transform.TransformPoint(new Point(0, 0));
//Rect winBounds = Window.Current.Bounds;
//Testing other UI items
itm.Visibility = Visibility.Visible;
// other UI stuff ...
}
}
}
}
I tried to make references to some UI elements such as Window.Content.Current and other elements (as a workaround) but was still getting the same System.Exception. I understand this has something to do with TimerElapsed being on a different thread than the UI thread and looked around for how to fix this but was not able to fix it.
My two issues are that I was not able to fix the thread marshalling issue (some issues with running things async) but more importantly that the solution seemed a bit convoluted, maybe more difficult than it needs to be.
CodePudding user response:
At first to fix the threading issue you have to use the Dispatcher of the UIElement:
await itm.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
itm.Visibility = Visibility.Visible;
});
And second, have you thought about using a tooltip for this? It should be very easy to implement:
<Grid Background="Transparent">
<Image Opacity="1" Source="/Assets/StoreLogo.png" Stretch="UniformToFill" />
<!--Tooltip implementation:-->
<ToolTipService.ToolTip>
<Image Source="/Assets/StoreLogo.png"/>
</ToolTipService.ToolTip>
</Grid>