I have a xaml page:
<Page x:Class="DailyStyleW8.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:DailyStyleW8"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:converters="using:DataTypes"
mc:Ignorable="d">
<Page.Resources>
<converters:PortableImageConverter x:Key="ImageConverter" />
</Page.Resources>
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<ProgressBar x:Name="loadingViewer"
IsIndeterminate="True"
Height="20" />
<FlipView x:Name="displayViewer"
ItemsSource="{Binding}"
Visibility="Collapsed">
<FlipView.ItemTemplate>
<DataTemplate>
<Grid>
<Image Source="{Binding Image,Converter={StaticResource ImageConverter}}" />
<TextBlock Text="{Binding Name}" />
</Grid>
</DataTemplate>
</FlipView.ItemTemplate>
</FlipView>
</Grid>
</Grid>
</Page>
and in the code behind file:
using DailyStyleApp;
using PortableAPI;
using System;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
namespace DailyStyleW8
{
/// <summary>
/// Display a list of recent updates to the user
/// </summary>
public sealed partial class MainPage : Page
{
Controller controller = new Controller();
public MainPage()
{
this.InitializeComponent();
}
/// <summary>
/// Invoked when this page is about to be displayed in a Frame.
/// </summary>
/// <param name="e">Event data that describes how this page was reached. The Parameter
/// property is typically used to configure the page.</param>
protected override void OnNavigatedTo(NavigationEventArgs e)
{
LoadContent();
}
private async void LoadContent()
{
var viewModel = await controller.GetMultiDayAsync(DateTime.Now, PortableAPIProvider.Storage.ReadFromSettings<int>("CacheDuration", 7));
displayViewer.ItemsSource = viewModel.Items;
displayViewer.Visibility = Windows.UI.Xaml.Visibility.Visible;
loadingViewer.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
}
}
}
Now when I run the code the LoadContent function is called correctly and the viewModel object is formed correctly. If I comment out the line displayViewer.ItemsSource = viewModel.Items; the ProgressBar visibility is changed as you would expect.
When that line is left in and stepped through all 4 lines inside the LoadContent method are run, however the FlipView is not updated with the new items and the ProgressBar visibility is not changed. viewModel.Items is of type List<T>.
I am even sure really what to be searching for here. I am guessing it's something wrong with the XAML and my binding?
The issue related to this question was actually to do with another section of code in the application. Elsewhere I had a series of async / await calls that were locking up the UI thread.
This prevented the scheduler from ever triggering the callbacks for the async. In short solution to the problem: Never call await on something that is called from the UI thread (and not via another async call).
Related
I have a small desktop app that loads numerous windows and pages into a main screen into a grid on a button click. The relevant page loads into the grid and users then enter data in text boxes, selected radio questions and so on. They can then copy the data entered and paste it into where they need to paste it. Simple macro type desktop app.
My main issue is when the users click from one loaded window and goes to another macro that then deletes all previously entered data. So if the user then goes back to a previous macro it reloads as new.
Each window/macro has a reset button and I would ultimately like to have all data saved until that is used or app is closed.
Thanks for any help!
Update: added code below. Simple app that has a main window/landing page and a grid. On button click grid loads a page (page2 or page3) dependent on which button is clicked. If A user enters text in text boxes, goes to a different page by clicking the other button and then goes to page with said button previously entered data is gone.
code breakdown:
main screen xaml
<Page
x:Class="test.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:test"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" Width="1001">
<Grid x:Name="loadgrid" Height="500" Background="White" Margin="423,110,61,110">
<Button Content="Button" Margin="-298,29,0,0" VerticalAlignment="Top" Width="113" Click="Button_Click"/>
<Button Content="Button" Margin="-298,97,0,0" VerticalAlignment="Top" Width="113" Click="Button_Click_1"/>
</Grid>
</Page>
main screen CS
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409
namespace test
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
loadgrid.Children.Add(new test.BlankPage1());
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
loadgrid.Children.Add(new test.page3());
}
}
}
page1 xaml
<Page x:Name="page2"
x:Class="test.BlankPage1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:test"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="#FFF57A7A" Width="400" Height="400">
<Grid>
<TextBox HorizontalAlignment="Center" Margin="0,53,0,0" Text="TextBox" TextWrapping="Wrap" VerticalAlignment="Top" Width="178" AllowFocusWhenDisabled="True"/>
<TextBox HorizontalAlignment="Center" Margin="0,116,0,0" Text="TextBox" TextWrapping="Wrap" VerticalAlignment="Top" Width="178"/>
<TextBox HorizontalAlignment="Center" Margin="0,168,0,0" Text="TextBox" TextWrapping="Wrap" VerticalAlignment="Top" Width="178"/>
</Grid>
</Page>
page1 cs
namespace test
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class BlankPage1 : Page
{
public BlankPage1()
{
this.InitializeComponent();
}
}
}
page3 xaml
<Page x:Name="page1"
x:Class="test.page3"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:test"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="#FF5C838F" Width="400" Height="400">
<Grid>
<TextBox HorizontalAlignment="Left" Margin="123,105,0,0" Text="TextBox" TextWrapping="Wrap" VerticalAlignment="Top" Width="173"/>
<TextBox HorizontalAlignment="Left" Margin="123,168,0,0" Text="TextBox" TextWrapping="Wrap" VerticalAlignment="Top" Width="173"/>
<TextBox HorizontalAlignment="Left" Margin="123,227,0,0" Text="TextBox" TextWrapping="Wrap" VerticalAlignment="Top" Width="173"/>
</Grid>
</Page>
page3 cs
namespace test
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class page3 : Page
{
public page3()
{
this.InitializeComponent();
}
}
}
hi you can enable the page cache mode at the start of the page it store the entire page cache data check boxes data n all.
this.NavigationCacheMode = Windows.UI.Xaml.Navigation.NavigationCacheMode.Enabled;
As #Ed_Plunkett said, if you want to save the state of user input, you can use MVVM. Simply put, the user's input is saved to a variable for the next page load.
But as far as your current code is concerned, Page is best to use Frame for navigation. If you need to add some controls directly to the Page, you can create a UserControl.
Here is the document about UserControl.
Here is the document about Navigation.
Best regards.
I am trying to change the language of the DocumentViewer from default English to German but with no success.
Being new to WPF, I really struggle to do this.
IMPORTANT: DocumentViewer is created in code behind, in response to the menu item click, and then it is added as main window's Content.
I have tried doing the following, but it seems to do nothing:
myDocumentViewer.Language = System.Windows.Markup.XmlLanguage.GetLanguage("de-DE");
No changes are made, DocumentViewer keeps English.
Googling for proper usage of the Language property, I found nothing useful.
QUESTION:
How can I set the language of the DocumentViewer (created with code) to German?
What you are trying to accomplish can be done, but not very easily.
I'll start by pointing out that your test machine needs to have the appropriate language resources installed to permit DocumentViewer to show you tooltips etc. in German. In practice, this means that you'll need to have German (Germany) language pack installed on your computer. See Language Packs for details.
What comes below is what I know to the best of my understanding:
WPF does not quite have an in-built infrastructure, as far as I can tell, to dynamically adapt to changes in either Thread.CurrentThread.CurrentUILanguage or to changes in xml:lang(which is equivalent to FrameworkElement.Language property.
WPF controls primarily utilize xml:lang to determine their UI language (assuming that the corresponding UI resources are available), and it is up to the application developer to hook that up with Thread.CurrentThread.CurrentUILanguage if so desired. This in itself is not very hard to do using data-binding, like this:
<DocumentViewer Language="{Binding UILanguage, ConverterCulture={x:Static glob:CultureInfo.InvariantCulture}}" />
That still does not mean that the control thus data-bound would adapt its UI language to changes in Thread.CurrentThread.CurrentUILanguage. Every time you need the UI language to be changed, you need to recreate the control, remove the old control from the visual tree, and add the new one. Roughly, the code would look somewhat like this:
private void ChangeCulture()
{
string ietfLanguageTag = "de-DE";
var cultureInfo = CultureInfo.GetCultureInfo(ietfLanguageTag);
Thread.CurrentThread.CurrentCulture = cultureInfo;
Thread.CurrentThread.CurrentUICulture = cultureInfo;
UILanguage = ietfLanguageTag;
var parent = VisualTreeHelper.GetParent(_documentViewer) as Grid;
int index = parent.Children.IndexOf(_documentViewer);
parent.Children.Remove(_documentViewer);
_documentViewer = new DocumentViewer();
parent.Children.Add(_documentViewer);
}
The above snippet assumes that the visual parent of the DocumentViewer is a Grid, and it is backed by the variable _documentViewer.
Generally, the above solution is too simplistic and is not well suited for MVVM scenarios (which is often the case in WPF applications). You might have data bindings to the DocumentViewer instance, and creating new instances would require that those bindings be recreated in code (if, on the other hand, there happen to be no data-bindings involved, and all settings are set in code, then the above approach would just work, I think).
You can further improve this by creating a simple user control that encapsulates a DocumentViewer, along with any interesting bindings you might wish to preserve. Your control would look like this:
XAML:
<UserControl x:Class="LocalizedDocumentViewer.CultureAwareDocumentViewer"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:glob="clr-namespace:System.Globalization;assembly=mscorlib"
xmlns:local="clr-namespace:LocalizedDocumentViewer"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="300"
d:DesignWidth="300"
mc:Ignorable="d">
<Grid>
<DocumentViewer DataContext="{Binding}" Language="{Binding UILanguage, ConverterCulture={x:Static glob:CultureInfo.InvariantCulture}}" />
</Grid>
XAML.cs
using System.Windows.Controls;
namespace LocalizedDocumentViewer
{
public partial class CultureAwareDocumentViewer : UserControl
{
public CultureAwareDocumentViewer()
{
InitializeComponent();
}
}
}
Now, you can easily include this in your main application UI, like shown below. The XAML below includes a couple of additional UI elements (buttons and labels) that would help show a complete example:
MainWindow XAML:
<Window x:Class="DocViewerLoc.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:DocViewerLoc"
xmlns:localizedDocumentViewer="clr-namespace:LocalizedDocumentViewer;assembly=LocalizedDocumentViewer"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="DocumentViewer Culture Change Demo"
Width="525"
Height="350"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
mc:Ignorable="d">
<Grid>
<!-- Row and Column Definitions -->
<!-- Define a small row on the top of the window to place buttons -->
<Grid.RowDefinitions>
<RowDefinition Height="25" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<!-- Controls -->
<Button Grid.Row="0"
Grid.Column="0"
Command="{Binding CultureChanger}"
CommandParameter="{Binding RelativeSource={RelativeSource Self},
Path=Content}">
en-us
</Button>
<Button Grid.Row="0"
Grid.Column="1"
Command="{Binding CultureChanger}"
CommandParameter="{Binding RelativeSource={RelativeSource Self},
Path=Content}">
de-DE
</Button>
<Label Grid.Row="0" Grid.Column="2"><-- Click on one of these buttons to change UI culture</Label>
<Grid Grid.Row="1" Grid.ColumnSpan="3">
<localizedDocumentViewer:CultureAwareDocumentViewer x:Name="_documentViewer" DataContext="{Binding}" />
</Grid>
</Grid>
The corresponding code-behind has a couple of dependency properties used to help communicate with the bindings in the above XAML.
MainWindow.xaml.cs
using System;
using System.Globalization;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
namespace DocViewerLoc
{
public partial class MainWindow : Window
{
public MainWindow()
{
CultureChanger = new SimpleCommand(ChangeCulture);
InitializeComponent();
}
/// <summary>
/// ChangeCulture is called when one of the buttons with caption
/// 'en-us' or 'de-DE' is pressed.
/// </summary>
/// <param name="parameter">
/// A string containing the caption 'en-us' or 'de-DE'.
/// </param>
private void ChangeCulture(object parameter)
{
string ietfLanguageTag = parameter as string;
if (ietfLanguageTag == null) return;
var cultureInfo = CultureInfo.GetCultureInfo(ietfLanguageTag);
Thread.CurrentThread.CurrentCulture = cultureInfo;
Thread.CurrentThread.CurrentUICulture = cultureInfo;
// This will ensure that CultureAwareDocumentViewer's Language property
// binds to the updated value set here when it is instantiated next.
UILanguage = ietfLanguageTag;
// Remove the old instance of _documentViewer from the UI.
var parent = VisualTreeHelper.GetParent(_documentViewer) as Grid;
int index = parent.Children.IndexOf(_documentViewer);
parent.Children.Remove(_documentViewer);
// Create a new instance of CultureAwareDocumentViewer. This will
// use the updated value of UILanguage bind it to its Language (xml:lang)
// property, thus resulting in the appropriate language resources being
// loaded.
_documentViewer = new LocalizedDocumentViewer.CultureAwareDocumentViewer();
// Now, add the _documentViewer instance back to the UI tree.
parent.Children.Add(_documentViewer);
}
/// <summary>
/// ICommand used to bind to en-us and de-DE buttons in the UI
/// </summary>
#region CultureChange
public SimpleCommand CultureChanger
{
get { return (SimpleCommand)GetValue(CultureChangerProperty); }
set { SetValue(CultureChangerProperty, value); }
}
// Using a DependencyProperty as the backing store for CultureChanger. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CultureChangerProperty =
DependencyProperty.Register("CultureChanger", typeof(SimpleCommand), typeof(MainWindow), new PropertyMetadata(default(SimpleCommand)));
#endregion
/// <summary>
/// UILanguage property used to bind to the FrameworkElement.Language (xml:lang) property
/// in the DocumentViewer object within the CultureAwareDocumentViewer control.
/// </summary>
#region UILanguage
public string UILanguage
{
get { return (string)GetValue(UILanguageProperty); }
set { SetValue(UILanguageProperty, value); }
}
// Using a DependencyProperty as the backing store for UILanguage. This enables animation, styling, binding, etc...
public static readonly DependencyProperty UILanguageProperty =
DependencyProperty.Register("UILanguage", typeof(string), typeof(MainWindow), new PropertyMetadata(Thread.CurrentThread.CurrentUICulture.IetfLanguageTag));
#endregion
}
/// <summary>
/// Simple implementation of the ICommand interface that delegates
/// Execute() to an Action<object>.
/// </summary>
public class SimpleCommand : ICommand
{
#pragma warning disable 67
public event EventHandler CanExecuteChanged;
#pragma warning restore 67
public SimpleCommand(Action<object> handler)
{
_handler = handler;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_handler?.Invoke(parameter);
}
private Action<object> _handler;
}
}
The below screenshot show the resulting application UI. Note that the resources in DocumentViewer would switch between English and German, but the rest of the UI would not (because we did not try to localize our application!).
Application showing en-us resources in DocumentViewer:
Application showing de-DE resources in DocumentViewer:
AFAIK you are setting it correctly.
I do not have experience with DocumentViewer, but setting CurrentUICulture does not translate. Setting CurrentUICulture selects between resources that you have in your application for different languages. See https://stackoverflow.com/a/1142840/5569663 for an example. I assume that Language of a DocumentViewer is the same.
Unluckily setting the Language property of your DocumentViewer won't work. The reason of this issue is related to the PresentationUI assembly (it is a standard one), which contains resources which affect DocumentViewer.
The main point is that tooltips and labels are hardcoded in these resources and they are only in english (at least considering .NET 4).
For example, this is how the DocumentViewer's print button is defined (in the themes/generic.baml resource):
<Button Name="PrintButton" x:Uid="Button_14" ToolTip="Print (Ctrl+P)" ToolTipService.ShowOnDisabled="True" Width="24px" Padding="2,2,2,2" Margin="2,2,2,2" VerticalAlignment="Center" Command="ApplicationCommands.Print" IsTabStop="True" TabIndex="0" Style="{DynamicResource {ComponentResourceKey TypeInTargetAssembly={x:Type ui:PresentationUIStyleResources}, ResourceId=PUIDocumentViewerButtonStyle}}" Background="{DynamicResource {ComponentResourceKey TypeInTargetAssembly={x:Type ui:PresentationUIStyleResources}, ResourceId=PUIDocumentViewerPrintButton}}" CommandTarget="{Binding Path=TemplatedParent, RelativeSource={RelativeSource TemplatedParent}}" />
As you can see is defined as "Print (Ctrl+P)". You will find the same situation for other labels which you would localize. Even if x:Uid properties are defined LocBaml won't work since PresentationUI is not a satellite assembly.
So a first solution could be: write your own DocumentViewer style and you can use the language that you prefer. The problem is that inside a DocumentViewer there is a control named FindToolBar. It is declared as internal, so probably it would be hard to redefine its style.
Then I propose an alternative solution to you. My idea is based on the fact that localizable children of a DocumentViewer have a name (you can use ILSpy to establish it).
So you need just to extend DocumentViewer in this way:
public class LocalizedDocumentViewer : DocumentViewer
{
public LocalizedDocumentViewer()
{
Loaded += new RoutedEventHandler(OnLoaded);
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
Button button = FindChild<Button>(this, "PrintButton");
button.ToolTip = Properties.Resources.PrintToolTip;
button = FindChild<Button>(this, "CopyButton");
button.ToolTip = Properties.Resources.CopyToolTip;
button = FindChild<Button>(this, "FindPreviousButton");
button.ToolTip = Properties.Resources.FindPreviousToolTip;
button = FindChild<Button>(this, "FindNextButton");
button.ToolTip = Properties.Resources.FindNextToolTip;
Label label = FindChild<Label>(this, "FindTextLabel");
label.Content = Properties.Resources.FindTextLabel;
/* and so on... */
}
public static T FindChild<T>(DependencyObject parent, string childName)
where T : DependencyObject
{
/* see the link for the code */
}
}
You can find the code of FindChild method here (take a look to CrimsonX's answer).
I know, it is a unelegant solution. I do not like it too. But I guess it is fast and it allows you to preserve the default style look.
I have a wpf application which has a main window and menu. This main window has a panel, and on clicking the menu item i create an instance of the user control and load the panel with the control.
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="" MinHeight="750" Height="Auto" MinWidth="1100" Width="Auto" WindowState="Maximized" ScrollViewer.VerticalScrollBarVisibility="Auto"
Loaded ="MainWindow_OnLoaded" Closing="Window_Closing">
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility ="Auto" SizeChanged="ScrollViewer_SizeChanged">
<Grid Width="Auto">
<Grid.RowDefinitions>
<RowDefinition Height="38"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<StackPanel Height="38" Width="Auto" Background="#09527B">
<Grid Margin="0,0,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition Width="70"></ColumnDefinition>
</Grid.ColumnDefinitions>
</Grid>
</StackPanel>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="189"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid Grid.Column="0">
<StackPanel>
<Expander Name="test" Header="Admin" Foreground="White" Margin="0,10,0,0">
<StackPanel Margin="20,0,0,0">
<Expander Header="Data" Foreground="White">
<StackPanel>
<TextBlock Text="Add/Edit UC1" Foreground="White" Margin="30,5,0,0" MouseDown="OpenUC1_MouseDown" MouseEnter="TextBlock_MouseEnter" MouseLeave="TextBlock_MouseLeave"/>
<TextBlock Text="Add/Edit UC2" Name="tbxBuild" Foreground="White" Margin="30,5,0,0" MouseDown="OpenUC2_MouseDown" MouseEnter="TextBlock_MouseEnter" MouseLeave="TextBlock_MouseLeave"/>
</StackPanel>
</Expander>
</StackPanel>
</Grid>
<StackPanel Grid.Column="1">
<Grid Name="pnlMain" Height ="Auto" VerticalAlignment="Top" HorizontalAlignment="Left">
</Grid>
</StackPanel>
</Grid>
</Grid>
</ScrollViewer>
</Window>
MainWindow.cs
private void OpenUC1_MouseDown(object sender, MouseButtonEventArgs e)
{
for (int i = 0; i < pnlMain.Children.Count; i++ )
{
pnlMain.Children.Remove(pnlMain.Children[i]);
}
using (UC2 _uc2= new UC2())
{
pnlMain.Children.Add(_uc2);
}
}
private void OpenUC2_MouseDown(object sender, MouseButtonEventArgs e)
{
for (int i = 0; i < pnlMain.Children.Count; i++ )
{
pnlMain.Children.Remove(pnlMain.Children[i]);
}
using (UC1 _uc1= new UC1())
{
pnlMain.Children.Add(_uc1);
}
}
My question is when I remove the control(UC1) from the main panel, when will that control be disposed?
Both the user control(UC1 and UC2) has the same view model attached to its data context. So i find that some of the methods in the removed user control(UC1) is called even though that is removed from the panel. The reason being, when a new instance of UC2 is created, there are some changes in the data model which in effect calls the dependent methods in UC1.
But if UC1 had been disposed this wouldn't happen. How can I make sure UC1 is disposed before instance of UC2 is created?
public UC1()
{
InitializeComponent();
this.DataContext = App.ViewModel.TestViewModel;
}
private void UC1_Unloaded(object sender, RoutedEventArgs e)
{
this.DataContext = null;
}
public UC2()
{
InitializeComponent();
this.DataContext = App.ViewModel.TestViewModel;
}
private void UC2_Unloaded(object sender, RoutedEventArgs e)
{
this.DataContext = null;
}
The unloaded method is not called immediately when the control is removed from the panel.
When I write and test code to dynamically add and remove a UserControl object from a window's visual tree, I find that the Unloaded event is raised just as expected.
In your own code example, there is at least one serious problem, and two incongruities:
The serious problem is how you are removing children. Your for loop is iterating by index through the children of the pnlMain object (a Grid). But removing any child invalidates the sequence of indexes! That is, the loop will first remove the child at index 0; this causes the child at index 1 to now become the child at index 0. But the loop increments the index before continuing, and will next remove the child at index 1. This child was originally at index 2. The code skips every other child (i.e. the ones originally at odd-numbered indexes), leaving half of them attached as children of the Grid.
Incongruity #1: I would expect a method with the phrase "OpenUC1" in the name to add an instance of UC1. However, your OpenUC1_MouseDown() method seems to be adding an instance of UC2 (and vice a versa for OpenUC2_MouseDown()). At the very least, there should be a comment in the code explaining why the code is different from what one might expect given the name of the method.
Incongruity #2: there is a using statement around the call to Add() when adding the UserControl objects. First, UserControl itself does not implement IDisposable, so unless your types have implemented that interface, that code is not even legal. Second, even if your UserControl subclasses do implement that interface, it does not seem like a very good idea to me to dispose an object that you've just created and which you are retaining in the visual tree (i.e. by adding it to the Grid's children).
Unfortunately, as I mentioned in my comment, without a good, minimal, complete code example that reliably reproduces your problem, it is impossible to say why your code does not behave as one would hope and/or expect it to. It is possible that any of the above points (but especially #1) are the cause of the behavior you're seeing, but I have no way to know for sure.
If after addressing those issues (or determining somehow that they are not problems…though if you can legitimately do that, I would argue that the code is still defective, in the sense that it's poor design), you find that your problem still exists, please edit your question so that it includes a good, minimal, complete code example that reliably reproduces the problem.
In the meantime, here is a simple code example that illustrates the basic behavior of the Unloaded event being raised just as expected when the object is removed from the visual tree. Note that while the correct way to remove all children from the Grid object's Children collection is to simply call the Clear() method (e.g. pnlMain.Children.Clear()), I have included an example of a explicit loop-based approach that does work.
XAML:
UserControl1.xaml
<UserControl x:Class="TestSO33289488UserControlUnloaded.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
Unloaded="UserControl_Unloaded"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TextBlock Text="UserControl" FontSize="36"/>
</Grid>
</UserControl>
MainWindow.xaml
<Window x:Class="TestSO33289488UserControlUnloaded.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">
<StackPanel>
<Button x:Name="button1" Content="Add UserControl"
HorizontalAlignment="Left" Click="Button_Click"/>
<Grid x:Name="grid1"/>
</StackPanel>
</Window>
C#:
UserControl1.xaml.cs
using System.Windows;
using System.Windows.Controls;
namespace TestSO33289488UserControlUnloaded
{
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
private void UserControl_Unloaded(object sender, RoutedEventArgs e)
{
MessageBox.Show("UserControl.Unloaded was raised");
}
}
}
MainWindow.xaml.cs
using System.Windows;
namespace TestSO33289488UserControlUnloaded
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private bool _removeUserControl;
private void Button_Click(object sender, RoutedEventArgs e)
{
if (_removeUserControl)
{
//grid1.Children.Clear();
// Calling Clear() is better, but if you really want to loop,
// it is possible to do correctly. For example:
while (grid1.Children.Count > 0)
{
grid1.Children.RemoveAt(grid1.Children.Count - 1);
}
button1.Content = "Add UserControl";
}
else
{
grid1.Children.Add(new UserControl1());
button1.Content = "Remove UserControl";
}
_removeUserControl = !_removeUserControl;
}
}
}
Quote from an MSDN forum entry about Loaded/Unloaded events:
The events are raised asynchronously, so there might be some delay
between the action that causes the event and the event itself. The
events are effectively put into a list and a task is added to the
dispatcher's queue. When that task runs, it raises the events on the
list.
So the answer is you can't predict when exactly these events will raised and you shouldn't expect that they will be called immediately after you removed a control from it's parent.
It's kinda difficult to give you a proper solution without seeing the full project, but here's a quick and dirty solution: rather than making sure that the given user controls' events are fired in time let's check the Parent property of the UC1/UC2 object before running the method. If the property is null then the UC1/UC2 object was removed and you should not execute that method.
But let me point out some problems with this code:
What's the point of the using block in the MouseDown event handlers? You create a user control object, add it to the panel and then immediately after that you call the Dispose method on it? (that's what the using block does in C#)
You don't need a for loop to remove all the children elements from a Panel control like a Grid. You can do that in one line. pnlMain.Children.Clear();
so I just created a basic canvas that has a event. Yet when I run this code the event is never actually hit. Im writing in c# for metro apps. What did I do wrong?
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<Canvas HorizontalAlignment="Left" Height="673" VerticalAlignment="Top" Width="1346" Margin="10,85,0,0" PointerMoved="Canvas_PointerMoved"/>
</Grid>
Heres my c# code
public MainPage()
{
this.InitializeComponent();
}
/// <summary>
/// Invoked when this page is about to be displayed in a Frame.
/// </summary>
/// <param name="e">Event data that describes how this page was reached. The Parameter
/// property is typically used to configure the page.</param>
protected override void OnNavigatedTo(NavigationEventArgs e)
{
}
private void Canvas_PointerMoved(object sender, PointerRoutedEventArgs e)
{
Debug.WriteLine("hit");
}
You've discovered a confusing quirk of Canvases. You have to set a background color in order for it to be hit tested.
So, for example, change your code to this and it will hit the event:
<Grid>
<Canvas Background="Blue" HorizontalAlignment="Left" Height="673" VerticalAlignment="Top" Width="1346" Margin="10,85,0,0" PointerMove="Canvas_PointerMoved"/>
</Grid>
But, one thing you'll need to consider is whether Canvas is the right kind of panel to use. It is extremely primitive and generally not used unless you need to rigidly define the layout or are micro-optimizing for performance.
I need to get up to speed with some intermediate things in WPF and I'm building this small application as my learning grounds.
I have a collection of five pictures. Each picture has certain data tied to it: owner, date, size, etc.
I'd like to be able to click that picture and load that information into display in the same window. I do NOT want to load the information in a new opened window.
Any suggestions on what to search for, or even a small verbal walk through of the process?
I'm using a Frame to load the initial five picture, but I don't know how to capture the click on the main parent window if the click is being registered inside the user control.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="22" />
<RowDefinition />
</Grid.RowDefinitions>
<Menu Grid.Row="0">
<MenuItem Header="_File" />
<MenuItem Header="_Edit" />
<MenuItem Header="_View" />
<MenuItem Header="_Help" />
</Menu>
<Frame Grid.Row="1" Name="contentFrame" Source="Roster.xaml" />
</Grid>
Then in Roster.xaml:
<UserControl x:Class="OracleOfLegends.Roster"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
//Lots of goodies here.
</UserControl>
You never mentioned about you Model Object ... so i will assume it as
Model
public class MyPicture : INotifyPropertyChanged
{
private string url;
private string name;
// Other fields
public Url
{
get{ return value;}
set{url=value;}
OnPropertyChanged("Url")
}
}
//Do same for other fields . I leave the implementation if INPC on you
ViewModel MyPictureListViewModel
public MyPictureListViewModel:INotifyPropertyChanged
{
ObservableCollection<MyPictureList> picList;
public PicList
{
get{return value;}
set{piclist=value;}
OnPropertyChanged("PicList")
}
//Fill the list with some methods... it depends on you
}
/// <summary>
/// Interaction logic for MyUserControl.xaml
/// </summary>
public partial class PicListDisplay
{
private readonly MyPictureListViewModel
myPictureListViewModel;
/// <summary>
/// PicListDisplay
/// </summary>
public PicListDisplay()
{
myPictureListViewModel= new MyPictureListViewModel();
this.DataContext = myPictureListViewModel;
InitializeComponent();
}
}
I would prefer to use ListView
<ListView Name="myPicListView" ItemsSource={Binding PicList}>
<ListView.ItemTemplate>
<Datatemplate>
<UserControls:Roster/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
// Here i am just Displaying the name you can put any control you want
<StackPanel DataContext="{Binding Path=SelectedItem,Elementname=myPicListView}">
<TextBlock Text="{Binding Path=Name}"></TextBlock>
</StackPanel> </UserControl>
Here i have made a listView and then on selecting the listview you can get the Data On the same window.... also you can create you Own view like that of Windows Explorer or else and use inside a list View
You can do pretty much the same thing in WPF using a Frame which you can navigate to a new UserControl containing the information.