synchronously show and hide controls in windows phone 8 - c#

In my windows phone 8 app I want to show a message before loading some items from database. After loading I want to hide the message again.
for examle my XAMLcode is like this:
<Grid Name="layoutRoot">
<Button Content="load more..."
Click="loadmore_clicked"/>
<TextBlock Name="loadingMessage"
Visibility="Collapsed"
Text="loading..." />
<phone:LongListSelector ItemsSource="{Binding Students}">
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<TextBlock Margin="10"
Text="{Binding name}" />
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
</Grid>
and in my c# code I am trying something like this:
private void loadmore_clicked(object sender, RoutedEventArgs e)
{
loadingMessage.Visibility = Visibility.Visible;
LoadMoreItemsForLongList(); // loading more items for the LongListSelector's ItemSource
loadingMessage.Visibility = Visibility.Collapsed;
}
The "loadingMessage" TextBlock never been shown. For testing I put Thread.Sleep() in the LoadMoreItemsForLongList() function. But it didn't work either.
My goal is to show a "loadingMessage" TextBlock before start loading more data to the LongListSelector and hide the "loadingMessage" TextBlock after finishing load of more data. How can I do that? any help will be appreciated.

Seems like that LoadMoreItemsForLongList is a blocking method. When visibility of the loadingMessage is set, it's visual apprearance won't get updated until control goes of the loadmore_clicked (which is running in UI thread) and UI can show the loadingMessage, but before that, it's Visibility set to Collapsed.
You run the LoadMoreItemsForLongList in another thread:
private void loadmore_clicked(object sender, RoutedEventArgs e)
{
loadingMessage.Visibility = Visibility.Visible;
Task.Run( () =>
LoadMoreItemsForLongList() // loading more items for the LongListSelector's ItemSource
).ContinueWith( () =>
loadingMessage.Visibility = Visibility.Collapsed;
);
}
But you need to be aware that all UI interactions in the LoadMoreItemsForLongList need to done in UI Thread. You may use Dispatcher:
void LoadMoreItemsForLongList() {
// Load list from DB
CoreWindow.GetForCurrentThread().Dispatcher.Current.RunAsync(
CoreDispatcherPriority.Normal, () => {
this.lstItems.ItemsSouce = ...;
});
}
But if you follow async design patterns and define LoadMoreItemsForLongList async, you can do this instead:
private async void loadmore_clicked(object sender, RoutedEventArgs e)
{
loadingMessage.Visibility = Visibility.Visible;
await LoadMoreItemsForLongListAsync();
loadingMessage.Visibility = Visibility.Collapsed;
}
This is the prefered way. and again, you still need to perfom UI actions of LoadMoreItemsForLongListAsync in UI thread.

Related

How to implement a delayed PointerEnter event into C# UWP project

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.
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>

How to keep WPF busy indicator responsive/animated when modal dialog is being parsed and laid out?

I implemented a sample application:
You can select the type that is used for the data grid that is then displayed in the dialog. If the user clicks the button, this code will be executed:
private void ShowDialog()
{
Window dialogView = (Window)Activator.CreateInstance(dialogs[selectedDialog]);
dialogView.ShowDialog();
}
Findings:
If “WPF” is selected, the dialog and DataGrid will be displayed immediately.
If “Infragistics” is selected, it takes more than a second for displaying the dialog and XamDataGrid.
If the “Infragistics” dialog is opened a second time, it will show up much faster.
Then I started profiling and clicked the button for “WPF” and two times “Infragistics”. Here is the timeline for these three clicks:
The XAML of the “Infragistics” dialog looks like this:
<Grid DataContext="{Binding DataGridDialog, Source={StaticResource Locator}}">
<igDP:XamDataGrid DataSource="{Binding Rows}" Width="300" Height="300"/>
</Grid>
The XAML of the “WPF” dialog looks like this:
<Grid DataContext="{Binding DataGridDialog, Source={StaticResource Locator}}">
<DataGrid ItemsSource="{Binding Rows}" Width="300" Height="300"/>
</Grid>
Anyway, the gap between the button click and the “Infragistics” dialog being displayed the very first time is not acceptable for a user. That is why I wrote the following code in the code behind for the "Infragistics" dialog that enables a busy indicator between the events “Initialized” and “Loaded”. Unfortunately the busy indicator's animation is not responsive:
public partial class InfragisticsDataGridDialogView : Window
{
private IUserInteractionService userInteractionService;
private TaskCompletionSource<object> tcs;
public InfragisticsDataGridDialogView(IUserInteractionService userInteractionService)
{
this.userInteractionService = userInteractionService;
Loaded += OnLoaded;
Initialized += OnInitialized;
InitializeComponent();
}
private async void OnInitialized(object sender, EventArgs e)
{
tcs = new TaskCompletionSource<object>();
await ShowBusyIndicatorAsync(tcs.Task);
}
private async Task ShowBusyIndicatorAsync(Task task)
{
await userInteractionService.ShowBusyIndication("Opening dialog", task);
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
tcs.SetResult(null);
}
}
Is there a way to make the busy indicator responsive? The busy indicator is a Grid in the main Window:
<Grid Visibility="{Binding UserInteractionService.ShowBusyIndicator, Converter={StaticResource BoolToVisibilityConverter}}">
<ProgressBar APProgressBar.SubTitle="{Binding UserInteractionService.BusyMessage}" IsIndeterminate="True" />
</Grid>

Modify UI Foreground Color after Navigating to Another Page and Back

I want to modify the font color of the header of a panorama control:
<phone:Panorama Name="MainPagePanorama"
Title="{Binding Path=LocalizedResources.ApplicationTitle, Source={StaticResource LocalizedStrings}}"
Background="{StaticResource QuotePaperBackground}"
SelectionChanged="MainPagePanorama_SelectionChanged">
<phone:Panorama.Foreground>
<SolidColorBrush x:Name="TitleColor" Color="{Binding Red, Source={StaticResource WP8AccentColors}}"/>
</phone:Panorama.Foreground>
...
</phone:Panorama>
And my event handler:
private void MainPagePanorama_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
selectedItem = MainPagePanorama.SelectedItem as PanoramaItem;
Deployment.Current.Dispatcher.BeginInvoke(async () =>
{
await Task.Delay(500);
TitleColor.Color = (selectedItem.Foreground as SolidColorBrush).Color;
});
}
This works perfectly fine until I navigate to another page and navigate back. I used the debugger to see that TitleColor.Color is still being changed everytime I swipe the screen, but the UI is not updated somehow.
Any help is appreciated. Thanks!
-Dan
Try with this code it could help you:
private async void MainPagePanorama_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
selectedItem = MainPagePanorama.SelectedItem as PanoramaItem;
await Task.Run(() =>
{
Thread.Sleep(100);
TitleColor.Color = (selectedItem.Foreground as SolidColorBrush).Color;
});
}
I think your code works (remarks below) but with the line:
TitleColor.Color = (selectedItem.Foreground as SolidColorBrush).Color;
you are changing the TitleColor to the same as it is - selectedItem.Foreground as SolidColorBrush is nothong more than <SolidColorBrush x:Name="TitleColor"....
To check it just repleace your code to:
TitleColor.Color = Colors.Brown;
and see what happens.
Remarks:
when you set a color in your code, you destroy your binding defined in xaml
I assume that you delay your color change on purpose
look that your code is already running on the Main thread so there is no need to invoke it from Dispatcher - it will be the same. So you can run your code like this:
private async void MainPagePanorama_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
PanoramaItem selectedItem = MainPagePanorama.SelectedItem as PanoramaItem;
await Task.Delay(500);
TitleColor.Color = Colors.Brown;
}

Waiting for Navigation Complete to continue

My team has been struggling for the Best Practices approach for handling the response from a Navigation for about 3 weeks now without a definitive answer. We have both a WPF and a Windows Phone 8 solution where we share a common code base.
For Phone 8, we display our company's splash screen and start initializing our data. Due to our complex nature, we have a very long list of steps to initialize before the application is fully operational.
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if (e.NavigationMode == NavigationMode.New)
{
BeginAppStartup();
return;
}
....
void BeginAppStartup()
{
// Initialization of settings and environment
At this point, we need to optionally display up to 5 different pages requesting additional data. So we check our commands, and if executable, then we navigate and optionally display a Communication Page, a Login Page, or several other possible pages.
if( condition )
DisplayLoginPage();
In WPF, this would be easy since we have modal dialogs and can wait for the user's input before continuing. But in the asynchronous world of WP8, we no longer have this.
To accommodate this platform, we have implemented a wide array of attempts, including saving the next command to execute. The only place that I believe that we are assured that the page is closed is in the OnNavigatedTo of the splash page.
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if (e.NavigationMode == NavigationMode.Back)
{
// If we are returning to the splash from another set up page, check if there are new actions to perform
if (_startupAction != null)
{
_startupAction();
return;
}
Unfortunately, this is only marginally acceptable since the Login page doesn't close properly since all of our action is in the UI thread. The code continues, but the splash page is hidden behind the still visible Login page.
We have also tried out AutoResetEvents, but since we must Navigate out of the UI thread, we can't block the UI thread. We've also tried Task.Run with similar issues.
// Doesn't work.
void ShowLoginPage()
{
if (condition)
{
_manualResetEvent.Reset();
NavigationService.Navigate(new Uri("/Views/Login.xaml", UriKind.Relative)
_manualResetEvent.WaitOne();
}
}
We've also tried the async/await tasks, but we encounter similar problems. I believe that this is the best solution, but we're not having any better luck than previously.
So back to the question: What is the Best Practice for Navigating from a splash page, optionally to a login page, and then to await for the login page to close completely before continuing?
This sounds like a very common scenario, yet I'm baffled! Thanks for your answers.
It is not difficult to provide a functionality similar to a modal dialog. I'm not sure if it is a great UI design decision, but it certainly can be done. This MSDN blog post describes how to do it with UserControl as a custom adorner. It was written in 2007, by that time there was no async/await nor WP8.
I'm going to show how to do a similar thing using Popup control (which is present in both WPF and WP8) and async/await. Here's the functional part:
private async void OpenExecuted(object sender, ExecutedRoutedEventArgs e)
{
await ShowPopup(this.firstPopup);
await ShowPopup(this.secondPopup);
}
Each popup can and should be data-bound to the ViewModel.
C# (a WPF app):
using System;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
namespace Wpf_22297935
{
public partial class MainWindow : Window
{
// http://stackoverflow.com/q/22297935/1768303
public MainWindow()
{
InitializeComponent();
}
EventHandler ProcessClosePopup = delegate { };
private void CloseExecuted(object sender, ExecutedRoutedEventArgs e)
{
this.ProcessClosePopup(this, EventArgs.Empty);
}
// show two popups with modal-like UI flow
private async void OpenExecuted(object sender, ExecutedRoutedEventArgs e)
{
await ShowPopup(this.firstPopup);
await ShowPopup(this.secondPopup);
}
private void CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
// helpers
async Task ShowPopup(Popup popup)
{
var tcs = new TaskCompletionSource<bool>();
EventHandler handler = (s, e) => tcs.TrySetResult(true);
this.ProcessClosePopup += handler;
try
{
EnableControls(false);
popup.IsEnabled = true;
popup.IsOpen = true;
await tcs.Task;
}
finally
{
EnableControls(true);
popup.IsOpen = false;
popup.IsEnabled = false;
this.ProcessClosePopup -= handler;
}
}
void EnableControls(bool enable)
{
// assume the root is a Panel control
var rootPanel = (Panel)this.Content;
foreach (var item in rootPanel.Children.Cast<UIElement>())
item.IsEnabled = enable;
}
}
}
XAML:
<Window x:Class="Wpf_22297935.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.Open" CanExecute="CanExecute" Executed="OpenExecuted" />
<CommandBinding Command="ApplicationCommands.Close" CanExecute="CanExecute" Executed="CloseExecuted"/>
</Window.CommandBindings>
<DockPanel>
<Border Padding="5">
<StackPanel>
<StackPanel>
<TextBlock>Main:</TextBlock>
<TextBox Height="20"></TextBox>
<Button Command="ApplicationCommands.Open" HorizontalAlignment="Left" Width="50">Open</Button>
</StackPanel>
<Popup Name="firstPopup" AllowsTransparency="true" Placement="Center">
<Border Background="DarkCyan" Padding="5">
<StackPanel Background="DarkCyan" Width="200" Height="200" HorizontalAlignment="Left">
<TextBlock>First:</TextBlock>
<TextBox Height="20"></TextBox>
<Button Command="ApplicationCommands.Close" HorizontalAlignment="Left" Width="50">Close</Button>
</StackPanel>
</Border>
</Popup>
<Popup Name="secondPopup" AllowsTransparency="true" Placement="Center">
<Border Background="DarkGray" Padding="5">
<StackPanel Background="DarkGray" Width="200" Height="200" HorizontalAlignment="Left">
<TextBlock>Second:</TextBlock>
<TextBox Height="20"></TextBox>
<Button Command="ApplicationCommands.Close" HorizontalAlignment="Left" Width="50">Close</Button>
</StackPanel>
</Border>
</Popup>
</StackPanel>
</Border>
</DockPanel>
</Window>
When dealing with such complex navigation, you should resort to creating your own navigation service. Instead of using NavigationService.Navigate, use your own wrapper over it.
In case of login page being after splash screen (and optionally), but before some other, you can always remove the page from backstack after navigation. So in this case you always navigate forward to another page and your custom service should remove last page if it is, say, a login page.

Adding images to StackPanel in WPF

I have a WPF-application that is looking for new images in a database and if something comes up, it adds the image into a list. When that event is raised I want it to add the image into a StackPanel.
First I tried just to insert the image, but got an InvalidOperationException saying "The calling thread must be STA, because many UI components require this." and came up with:
public void Instance_GraphicChanged(object sender, PropertyChangedEventArgs e)
{
foreach (Model.Graphic item in Model.IncomingCall.Instance.Graphics)
{
if(!_strings.Contains(item.ImageId.ToString()))
{
Thread thread = new Thread( new ThreadStart(
delegate()
{
//sp_images StackPanel for Images
sp_images.Dispatcher.Invoke(
DispatcherPriority.Normal, new Action(
delegate()
{
Image img = new Image();
img.Source = item.ImageObj; //ImageObj returns a BitmapImage
sp_images.Children.Add(img);
}
));
}
));
_strings.Add(item.ImageId.ToString());
}
}
}
This does not throw any kind of exception, but actually nothing happens...
In reference to my comment, you could try something like this:
XAML
<!-- ... Other XAML Code ... -->
<ItemsControl x:Name="sp_images">
<ItemsControl.ItemsPanel>
<StackPanel Orientation="Horizontal" />
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Image Source="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Code Behind
private readonly HashSet<string> mImageIds = new HashSet<string>();
private readonly ObservableCollection<BitmapImage> mImages = new ObservableCollection<BitmapImage>();
// ... Inside the constructor
{
InitializeComponent();
sp_images.ItemsSource = mImages;
}
public void Instance_GraphicChanged(object sender, PropertyChangedEventArgs e)
{
foreach (Model.Graphic item in Model.IncomingCall.Instance.Graphics)
{
// Have we already seen the image
if (mImageIds.Add(item.ImageId.ToString()))
{
// We've not seen the image yet, so add it to the collection
// Note: We must invoke this on the Dispatcher thread.
this.Dispatcher.BeginInvoke((Action)delegate()
{
mImages.Add(item.ImageObj);
});
}
}
}
This should bypass any cross-thread exceptions you might have had before. It should also allow you to easily add new images to the ObservableCollection, which will automatically update the UI with images. Also, the use of the ItemTemplate means you don't have to actually build the UI every time yourself; WPF will handle this for you.
See here for more information on using the ObservableCollection. Also, refer to this StackOverflow question for an explanation on the container templating.

Categories

Resources