WebView2 EnsureCoreWebView2Async never returns - wpf

In my WPF application I need to show an html string, and I know that I need to call EnsureCoreWebView2Async method before calling NavigateToString because otherwise the CoreWebView will be null and I would have an exception. The problem is that awaiting EnsureCoreWebView2Async never ends.
I created a little application in order to reproduce the problem (and excluding issues related to my big project) and the problem is the same.
If I set the WebView2's Source property passing an Url, then it works!
If I call NavigateToString without calling EnsureCoreWebView2Async, I get an exception (as expected).
If I call EnsureCoreWebView2Async before calling NavigateToString or before setting the Source property (it should not be a problem, because calling it many times should not have any effect according to the documentation) then it hangs forever.
No exceptions are raised by the call, and no messages in the console. Very frustrating.
Here are the code for the sample application (I main window with 2 buttons, one that opens the url and one that load the html string - the first one works):
<Window x:Class="WebViewApp.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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WebViewApp" xmlns:wpf="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<StackPanel>
<Button Width="200" Height="40" Click="OpenUrl_Click">Open url</Button>
<Button Width="200" Height="40" Click="OpenHtml_Click">Open html</Button>
</StackPanel></Window>
MainWindow behind code:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private async void OpenUrl_Click(object sender, RoutedEventArgs e)
{
var browserWindow = new WebWindow();
//await browserWindow.Initialize(); // this also never completes
browserWindow.OpenUrl("http://www.microsoft.com");
browserWindow.Show();
}
private async void OpenHtml_Click(object sender, RoutedEventArgs e)
{
var browserWindow = new WebWindow();
await browserWindow.Initialize();
browserWindow.LoadHtml(#"<html><head><title>Test title</title></head><body><p>Test</p></body></html>");
browserWindow.Show();
}
}
Here is the browserView xaml (add the WebView2 nuget package):
<Window x:Class="WebViewApp.WebWindow"
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WebViewApp" xmlns:wpf="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
mc:Ignorable="d"
Title="WebWindow" Height="450" Width="800">
<Grid>
<wpf:WebView2 Name="WpfBrowser" />
</Grid></Window>
and the code behind:
public partial class WebWindow : Window
{
public WebWindow()
{
InitializeComponent();
}
public async Task Initialize()
{
try
{
await WpfBrowser.EnsureCoreWebView2Async(); // never completes
}
catch (Exception exception)
{
Console.WriteLine(exception.Message);
}
}
public void LoadHtml(string html)
{
WpfBrowser.NavigateToString(html);
}
public void OpenUrl(string url)
{
WpfBrowser.Source = new Uri(url);
}
}
I also tried to change the Target platform, but nothing changed.

You should defer to initialize the webview until OnContentRendered is called. I changed your code and it worked as a charm. See the changes below.
public partial class WebWindow : Window
{
private string _url;
private string _html;
public WebWindow()
{
InitializeComponent();
}
protected override async void OnContentRendered(EventArgs e)
{
base.OnContentRendered(e);
try
{
var webView2Environment = await CoreWebView2Environment.CreateAsync();
await WpfBrowser.EnsureCoreWebView2Async(webView2Environment);
if(!string.IsNullOrEmpty(_url))
WpfBrowser.Source = new Uri(_url);
else if(!string.IsNullOrEmpty(_html))
WpfBrowser.NavigateToString(_html);
}
catch (Exception exception)
{
Console.WriteLine(exception.Message);
}
}
public void ShowFromUrl(string url)
{
_url = url;
Show();
}
public void ShowFromHtml(string html)
{
_html = html;
Show();
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private async void OpenUrl_Click(object sender, RoutedEventArgs e)
{
var browserWindow = new WebWindow();
browserWindow.ShowFromUrl("http://www.microsoft.com");
}
private async void OpenHtml_Click(object sender, RoutedEventArgs e)
{
var browserWindow = new WebWindow();
browserWindow.ShowFromHtml(#"<html><head><title>Test title</title></head><body><p>Testsdfsdfsdfsdfdsfdsfklsdkflkdlf<br>dsfdsfdsfdsfsdfdsfds</p></body></html>");
}
}
You can also check out the code from my git repo that does something similar.
KioskBrowser (GitHub)

Related

UI component does not exist in current context when code moved to class from main UI thread

I currently have working code which is implemented in MainWindow.xaml.cs that I am trying to move to a class which is giving me an error that my UI label does not exist in the current context.
Here is the code that works in the MainWindow:
public partial class MainWindow : Window
{
......
private RX consumer = new RX();
private void Window_Loaded(object sender, RoutedEventArgs e)
{
try
{
Task backgroundDBTask = Task.Factory.StartNew(() => { Consumer(consumer);}, TaskCreationOptions.LongRunning);
}
}
public void Consumer(Consumer consumer)
{
while (true)
{
Thread.Sleep(1000);
.......
Dispatcher.BeginInvoke(new Action(() =>
{
mylbl.Content = value.ToString();
}), DispatcherPriority.Background);
}
}
Then I tried moving the code to a separate class:
public partial class MainWindow : Window
{
....
private RX consumer = new RX();
private void Window_Loaded(object sender, RoutedEventArgs e)
{
try
{
Task backgroundDBTask = Task.Factory.StartNew(() => { consumer.ProcessMessages(); }, TaskCreationOptions.LongRunning);
}
}
}
public class RX
{
public void ProcessMessages()
{
while (true)
{
Thread.Sleep(1000);
....
var m_dispatcher = Application.Current.MainWindow;
m_dispatcher.Dispatcher.BeginInvoke(new Action(() =>
{
mylbl.Content = value.ToString();
}), DispatcherPriority.Background);
}
}
}
I'm getting the error on:
mylbl.Content = value.ToString();
from the class RX. I tried this as recommended var m_dispatcher = Application.Current.MainWindow to get to the MainWindow thread but its still giving an error.
You cannot access mylbl from other classes , other than MyWindow since it is defined there .
You can implement MVVM and bind the content property to string in view model and update the content .
or segregate your business logic to separate class and expose this to MyWindow.Xaml.cs.
You can have a public method which returns "value" in RX . and you can update your content in MyWindow.xaml.cs by accessing this method
or pass Label instance to ProcessMessage method and update the content. Of course,add a reference System.Windows.Controls in your class.
However this is not a good design . I suggest you to go through MVVM.
public void ProcessMessages(Label mylbl)
{
while (true)
{
Thread.Sleep(1000);
....
var m_dispatcher = Application.Current.MainWindow;
m_dispatcher.Dispatcher.BeginInvoke(new Action(() =>
{
mylbl.Content = value.ToString();
}), DispatcherPriority.Background);
}
}
and caller will look like this
public partial class MainWindow : Window
{
....
private RX consumer = new RX();
private void Window_Loaded(object sender, RoutedEventArgs e)
{
try
{
Task backgroundDBTask = Task.Factory.StartNew(() => { consumer.ProcessMessages(mylbl); }, TaskCreationOptions.LongRunning);
}
}
}
As suggested by Clemens , i am updating solution in MVVM way.
XAML Part:
<Window x:Class="MvvmExample.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:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MvvmExample"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding LoadedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<Grid >
<Label Content="{Binding LableContent}" Height="100" Width="500" Foreground="Red"/>
</Grid>
</Window>
I am binding LableContent string property to Content property of Label. And setting data content at the top to my View Model. Also,to bind event to command i have used interactivity dll.
ViewModel will look like this.
public class ViewModel : INotifyPropertyChanged
{
#region Constants and Enums
#endregion
#region Private and Protected Member Variables
private string _lableContent;
#endregion
#region Private and Protected Methods
private void OnLoaded(object obj)
{
Task.Factory.StartNew(() => { ProcessMessages(); }, TaskCreationOptions.LongRunning);
}
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
#region Constructors
public ViewModel()
{
LoadedCommand = new RelayCommand(OnLoaded);
}
#endregion
#region Public Properties
public event PropertyChangedEventHandler PropertyChanged;
public string LableContent
{
get
{
return _lableContent;
}
set
{
_lableContent = value;
OnPropertyChanged(nameof(LableContent));
}
}
public ICommand LoadedCommand { get; }
#endregion
#region Public Methods
public void ProcessMessages()
{
while (true)
{
Thread.Sleep(1000);
LableContent = "your value field";
}
}
#endregion
}
I have used ICommand implementation for commands .
Also i have used INotifyPropertyChanged for binding .
I assume you have the knowledge about following topics , If not there are plenty of help available in stack overflow on these
INotifyProertyChanged
Event to command binding
What is data context and how to set the data context
what is ICommand and implementing ICommand

WPF Commands. Textbox does not handle char mapped as a hotkey

I'm stuck with wpf commands and "non-trivial" hotkeys. I want to map "+" key to some command. Within i want it to keep working with any textbox. Below is the sample
Commands.cs
public static class Commands
{
private static readonly ICommand _someCommand;
static Commands()
{
_someCommand = new RoutedCommand("cmd", typeof(Commands), new InputGestureCollection { new KeyGesture(Key.OemPlus), new KeyGesture(Key.Add) });
}
public static ICommand SomeCommand
{
get { return _someCommand; }
}
}
MainWindow.xaml
<Window x:Class="WpfHotkeysTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:wpfHotkeysTest="clr-namespace:WpfHotkeysTest"
Title="MainWindow" Height="350" Width="525">
<Window.CommandBindings>
<CommandBinding Command="wpfHotkeysTest:Commands.SomeCommand" Executed="CommandBinding_OnExecuted"></CommandBinding>
</Window.CommandBindings>
<TextBox></TextBox>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void CommandBinding_OnExecuted(object sender, ExecutedRoutedEventArgs e)
{
Debug.WriteLine("COMMAND! " + e.Source);
}
}
The problem is when im focused on the textbox it does not handle pressed "+" key before command is executed.
I want my key be displayed, but how do i achieve this in the best way?
UPD
I dont want to execute command if the key was handled by the text box.
I know there is property CanExecuteRoutingEventArgs.ContinueRouting. But it executes both command and textbox handling
The workaround I've found so far
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void CommandBinding_OnExecuted(object sender, ExecutedRoutedEventArgs e)
{
Debug.WriteLine("COMMAND! " + e.Source);
}
private void CommandBinding_OnCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
if (IsEditableControlSelected())
{
e.ContinueRouting = true;
return;
}
e.CanExecute = true;
}
private bool IsEditableControlSelected()
{
return Keyboard.FocusedElement is TextBox;
}
}
This brings tears to my eyes, but at least this is better than nothing. Waiting for more solutions

How to open a child Window like a splash screen before MainWindow in WPF?

I have two xaml. One is MainWindow and other is NewWindow.
I want show NewWindow 5 seconds, when program is run.
And after 5 seconds, I want show MainWindow.
How to change xaml in WPF?
Here is MainWindow.
<Window x:Class="WpfApplication2.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">
<Grid>
</Grid>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
Here is NewWindow.
<Window x:Class="WpfApplication2.NewWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="NewWindow" Height="300" Width="300">
<Grid>
</Grid>
public partial class NewWindow : Window
{
public NewWindow()
{
InitializeComponent();
}
}
1) First, we need to stop MainWindow from opening as soon as we run the Application. To do this, first remove the StartupUri="MainWindow.xaml" setting from the App.xaml file and replace it by setting the Startup property instead:
<Application x:Class="AppName.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Startup="App_Startup">
2) Then, add the handler for the Application.Startup event and launch your child (or splash screen) Window:
private SplashScreen splashScreen;
...
public void App_Startup(object sender, StartupEventArgs e)
{
// Open your child Window here
splashScreen = new SplashScreen();
splashScreen.Show();
}
3) At this point, there are several different ways to go, dependent on whether you need to wait for the SplashScreen Window to do anything or not. In this particular question, the requirement is to simply open the MainWindow after 5 seconds, so we'll need a DispatcherTimer:
public void App_Startup(object sender, StartupEventArgs e)
{
// Open your child Window here
splashScreen = new SplashScreen();
splashScreen.Show();
// Initialise timer
DispatcherTimer timer = new DispatcherTimer();
timer.Interval = new TimeSpan(0, 5, 0);
timer.Tick += Timer_Tick;
}
...
private void Timer_Tick(object sender, EventArgs e)
{
splashScreen.Close();
MainWindow mainWindow = new MainWindow();
mainWindow.Show();
}
That's it.
There are plenty ways to do this. As some people suggested i suggest too to DO NOT DO THIS if you're trying to create a splash screen, there are better ways to do that. But.. here what you asked for:
using System.ComponentModel; //Remember to add this
public partial class MainWindow : Window
{
private BackgroundWorker waitingWorker = new BackgroundWorker();
private NewWindow myNewWindow = new NewWindow();
public MainWindow()
{
InitializeComponent();
waitingWorker.DoWork += waitingWorker_DoWork;
waitingWorker.RunWorkerCompleted += waitingWorker_RunWorkerCompleted;
waitingWorker.RunWorkerAsync();
}
private void waitingWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
myNewWindow.Show();
this.Close();
}
private void waitingWorker_DoWork(object sender, DoWorkEventArgs e)
{
Thread.Sleep(5000);
}
}
it's a simple background worker that waits for 5 seconds, then opens the NewWindow and close MainWindow. Yes, you can do it without background worker too, but Thread.Sleep(5000); will totally freeze your GUI and make your little app unresponsive, so you need another thread to wait while the main thread can keep your GUI alive. I suggest you to study at least how a background worker works.
HERE the official MSDN documentation, but google is your friend and you can find tons of tutorial and explanation about it
Here is another way to do it:
Set Startup to "App_Startup" as shown in one of the other posts.
<Application x:Class="AppName.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Startup="App_Startup">
And in App_OnStartup:
private async void App_Startup(object sender, StartupEventArgs e)
{
var splash = new SplashWindow();
splash.Show();
await Task.Delay(5000);
var mainWindow = new MainWindow();
mainWindow.Show();
splash.Close();
}
The mainWindow should also be loaded before closing the splashScreen This way your splashscreen shows as long as it is loading.You can add additional time in the splashScreen.Close() Function.
private SplashScreen splashScreen;
private void App_Startup(object sender, StartupEventArgs e)
{
splashScreen = new SplashScreen("SplashScreen1.png"); // Or new WPF window
splashScreen.Show(false);
MainWindow mainWindow = new MainWindow();
splashScreen.Close(new TimeSpan(0, 0, 3));
mainWindow.Show();
}

wpf databound custom control - why are changes to my datasource not detected by the custom control?

I have a custom control and a view model object. A property on the view model is bound to the custom control and I can see that the custom control actually receives the vaule from the view model object - yet my handler code (GeometryText.Set) is not executed. What am I doing wrong?!
Notice the event handlers on custom control where I've placed breakpoints- if I change the size of the window, I can inspect the GeometryText property in the watch window - and it's clearly updated in the cases where I expect it to.
Thanks for any input,
Anders, Denmark
ComponentDrawing.xaml.cs
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using Rap1D.ServiceLayer.Interfaces.Services;
using StructureMap;
namespace Rap1D.Rap1D_WPF.Controls
{
/// <summary>
/// Interaction logic for ComponentDrawing.xaml
/// </summary>
public partial class ComponentDrawing
{
public static DependencyProperty GeometryTextProperty =DependencyProperty.Register("GeometryText", typeof (string), typeof (ComponentDrawing), new FrameworkPropertyMetadata
(
"",
FrameworkPropertyMetadataOptions
.
None));
private Canvas _canvas;
public ComponentDrawing()
{
InitializeComponent();
}
public string GeometryText
{
get { return ((string) GetValue(GeometryTextProperty)); }
set
{
SetValue(GeometryTextProperty, value);
ReadGeometryTextIntoDrawing(value);
}
}
private void ReadGeometryTextIntoDrawing(string fileText)
{
// Allow control to be visible at design time without errors shown.
// I.e. - don't execute code below at design time.
if (DesignerProperties.GetIsInDesignMode(this))
return;
// OK - we are a running application
//if (_canvas != null)
// return;
// OK - this is first time (-ish) we are running
if (ActualWidth == 0)
return;
// We have a valid screen to pain on
var componentDrawingService = ObjectFactory.GetInstance<IComponentDrawingService>();
//var commandTextProvider = ObjectFactory.GetInstance<ICommandTextProvider>();
//var fileText = ((IViewModelBase) DataContext).GeometryText;
// If getting the file text fails for some reason, just abort to avoid further problems.
if (fileText == null)
return;
var pg = componentDrawingService.GetDrawings(fileText, 0, ActualWidth, 0, ActualHeight);
_canvas = new Canvas();
foreach (var path in pg)
{
_canvas.Children.Add(path);
}
Content = _canvas;
}
private void UserControl_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
//ReadGeometryTextIntoDrawing();
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
//ReadGeometryTextIntoDrawing();
}
private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e)
{
//ReadGeometryTextIntoDrawing();
}
}
}
ComponentDrawing.xaml:
<UserControl x:Class="Rap1D.Rap1D_WPF.Controls.ComponentDrawing" 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"
DataContextChanged="UserControl_DataContextChanged" Loaded="UserControl_Loaded" SizeChanged="UserControl_SizeChanged">
<Grid Background="White">
<Path Stroke="Black"></Path>
</Grid>
</UserControl>
Usage:
<Controls:RadPane x:Class="Rap1D.Rap1D_WPF.Controls.ProductComponentDetails" 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" xmlns:Controls="clr-namespace:Telerik.Windows.Controls;assembly=Telerik.Windows.Controls.Docking" xmlns:Controls1="clr-namespace:Rap1D.Rap1D_WPF.Controls" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300" Header="{Binding DisplayName}">
<Controls1:ComponentDrawing GeometryText="{Binding GeometryText}" />
</Controls:RadPane>
view model object (implementing INotifyPropertyChanged):
using System;
using Microsoft.Practices.Prism.Events;
using Rap1D.ExternalInterfaceWrappers.Interfaces;
using Rap1D.ModelLayer.Interfaces.Adapters;
using Rap1D.ModelLayer.Interfaces.Structure;
using Rap1D.ServiceLayer.Interfaces.Adapters;
using Rap1D.ServiceLayer.Interfaces.Providers;
using Rap1D.ViewModelLayer.Interfaces;
namespace Rap1D.ViewModelLayer.Implementations
{
public class ProductComponentViewModel : TreeViewItemViewModel, IProductComponentViewModel
{
...
public override string GeometryText
{
get
{
var pentaResponse = _commandTextProvider.GetCommandText(ProductComponent);
return DateTime.Now.ToString()+ pentaResponse.Payload;
}
}
...
}
}
Dependency property setters are not invoked if changed by binding. If you want to somehow react on dependency property value changing you should register a callback in property metadata:
http://msdn.microsoft.com/en-us/library/ms557294.aspx
something like that (not sure it is compilable, let me know if something wrong):
public static DependencyProperty GeometryTextProperty =
DependencyProperty.Register(... , new FrameworkPropertyMetadata(GeometryTextCallback));
public static void GeometryTextCallback(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
// cast source to your type and invoke method from your setter
((ComponentDrawing)source)ReadGeometryTextIntoDrawing(value);
}

Why are Silverlight ContentControls not garbage collected?

I've been investigating why some of my controls aren't being garbage collected and noticed it's easy to prevent simple controls that inherit from ContentControl from ever being destroyed. Here's an example:
Here is my custom ContentControl:
public class MyCustomControl : ContentControl
{
public MyCustomControl()
{
Debug.WriteLine("Constructed");
}
~MyCustomControl()
{
Debug.WriteLine("Destroyed");
}
}
Now if I put it on a page like so:
<navigation:Page x:Class="SimpleTestBed.Views.CustomControl"
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
xmlns:local="clr-namespace:SimpleTestBed"
d:DesignWidth="640" d:DesignHeight="480"
Title="CustomControl Page">
<Grid x:Name="LayoutRoot">
<StackPanel>
<local:MyCustomControl>
<TextBox Text="{Binding SomeProperty,Mode=TwoWay}"></TextBox>
</local:MyCustomControl>
</StackPanel>
</Grid>
With the following code behind:
public partial class CustomControl : Page
{
public CustomControl()
{
InitializeComponent();
this.DataContext = new CustomControlViewModel();
this.Unloaded += new RoutedEventHandler(OnUnloaded);
}
void OnUnloaded(object sender, RoutedEventArgs e)
{
this.DataContext = null;
}
// Executes when the user navigates to this page.
protected override void OnNavigatedTo(NavigationEventArgs e)
{
}
}
Then the view model is:
public class CustomControlViewModel : INotifyPropertyChanged
{
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
RaisePropertyChanged(propertyName);
}
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
private string _someProperty = "Initial Value";
public string SomeProperty
{
get { return _someProperty; }
set
{
if (_someProperty != value)
{
string oldValue = _someProperty;
_someProperty = value;
OnPropertyChanged("SomeProperty");
OnSomePropertyChanged(oldValue, value);
}
}
}
protected virtual void OnSomePropertyChanged(string oldValue, string newValue)
{
}
}
Now when I navigate away from this page and try garbage collecting with GC.Collect(), as long if I've made no changes to the text in the Textbox, the ContentControl and Page are destroyed as expected by the GC. But if I've edited some text and navigated away from the page and then tried to GC.Collect(), the ContentControl doesn't get garbage collected.
Can anyone explain this behavior?
Actually, you can force the GC to collect the control by 'flickering' the Template of the control when you unload:
void MyCustomControl_Unloaded(object sender, RoutedEventArgs e)
{
Debug.WriteLine("MyCustomControl Unloaded");
ControlTemplate oldTemplate = this.Template;
this.Template = null;
this.Template = oldTemplate;
}
I presume this destroys the current visual tree losing references of the tree's first component to its parent (the custom control). It certainly forces the control to recall OnApplyTemplate when the control is reloaded.
Is this the correct pattern for developing Silverlight controls without leaking? If so, it strikes me as a bit bizarre that the template isn't disposed automatically when the control unloads.
A good account of this behavior would be much appreciated as it goes right to the heart of the life-cycle of Silverlight controls.
My experience showed that memory leaks in Silverlight are coused by top 2 reason :
Events => Make sure that you remove attached events once when it is not needed or in class destructor.
Templates => Solution define templates in Resource section

Resources