How to wait for the event inside a function? - wpf

I'm trying to write a function in VB.Net WPF application for retrieving document’s height in MS WebBrowser control. I can get this value only after some time, when page has been rendered. So, I tried:
Private Function GetHeight(ByVal htmlstring As String) As Integer
Dim wb As New WebBrowser 'Declare WebBrowser
wb.Width = 940 'set Width
wb.NavigateToString(htmlstring) 'Navigate to content
Do Until wb.IsLoaded 'Wait until page is rendered
Loop
'Get DOM Document
Dim doc As mshtml.HTMLDocument = wb.Document
'Get sought value
Dim RetVal As Integer = CInt(doc.body.getAttribute("scrollHeight").ToString)
doc = Nothing : wb.Dispose() : wb = Nothing 'Free variables
Return RetVal 'Return value
End Function
But calling such function causes application to freeze. What should I do? Do I need to implement Async and Await keywords, as well as Threading.Tasks and how to achieve this?

According to MSDN, IsLoaded is a framework element property that indicates whether or not the control has been loaded for presentation, not whether the WebBrowser control's web page has been loaded.
While I question why you would instantiate a browser and have it navigate to a page in a method called "GetHeight"... what I think you want to do here is subscribe to the LoadCompleted event: https://msdn.microsoft.com/en-us/library/system.windows.controls.webbrowser.loadcompleted(v=vs.110).aspx
Perhaps you can construct your webbrowser and have it navigate in a different method, and subscribe to the LoadCompleted event with your GetHeight method?
Edit I neglected to mention that because you declared your browser control outside of xaml, the control will never be loaded (and thus, navgiation won't work) because it's not in the visual tree of the WPF application. You'll have to either use a webbrowser already declared in xaml (which I would recommend), or add it programmatically with something like this:
<Window x:Class="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 x:Name="MainPanel">
<Button Click="Button_Click"> Clicky</Button>
</StackPanel>
Class MainWindow
Private Sub Button_Click(sender As Object, e As RoutedEventArgs)
Dim wb As New WebBrowser 'Declare WebBrowser
wb.Width = 940 'set Width
Me.MainPanel.Children.Add(wb)
wb.NavigateToString("www.stackoverflow.com")
AddHandler wb.LoadCompleted, Sub(s, ee) DoSomething()
End Sub
Private Sub DoSomething()
MessageBox.Show("blah")
End Sub
End Class

Related

Internet Explorer instance DocumentComplete not firing

I am trying to create an instance of Internet Explorer from a WPF application, load a saved local file, and do some further processing once the file is loaded. However, although the file is visible in the Internet Explorer window, the DocumentComplete event never fires:
'static field
Dim iex As ShDocVw.InternetExplorer
Public Sub DoStuff()
Dim path = "c:\test.htm"
iex = New SHDocVw.InternetExplorer
iex.Visible = True
AddHandler iex.DocumentComplete, Sub(o As Object, ByRef url As Object)
'This code is never executed
Dim i = 5
End Sub
iex.Navigate2(path)
End Sub
When I navigate to a non-local URL (e.g. http://www.google.com) the DocumentComplete event does fire.
The same behavior exists for the NavigateComplete2 event.
I tried using a class member method instead of a lambda expression (maybe the lambda expression is going out of scope once the method exits?) using both AddressOf and Handles, but that didn't help.
What do I have to do to have the DocumentComplete event fire?
(NB: The page has no frames.)
Update
This code is being used in a class library, and I therefore cannot use the WebBrowser control, as it cannot be instantiated in code.
As SimonMourier points out in the comments, a WebBrowser can be instantiated in code:
Dim wb = New WebBrowser
AddHandler wb.LoadCompleted, Sub(s, e)
Dim i = 5
End Sub
wb.Navigate(path)
Although the LoadCompleted event still doesn't fire, the Navigated event does, and it appears to be sufficient for my purposes. (Apparently the WebBrowser has to be visible in order for LoadCompleted to fire -- see here and here -- and since I am not using the WebBrowser in the context of a window, I don't think this is even possible in my case.)
You should use the out-of-the-box standard WebBrowser Control that ships with WPF (there is another one for Winforms apps). It has all the basic events directly supported.
Should you miss some Winforms feature like IsWebBrowserContextMenuEnabled or ScriptErrorsSuppressed, I suggest you refer to my answer in this question on SO: How to deactivate "right click" on WPF Webbrowser Control?
You only need interop in these special cases or if you need to get ahold on the native underlying IE's Document Object Model (DOM), interfaces like IHTMLDocument2, etc.
Instead of using ShDocVw.InternetExplorer you can use WebBrowser control provided by WPF:
<Window x:Class="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" Loaded="Window_Loaded">
<Grid>
<WebBrowser x:Name="webBrowser" Visibility="Visible" />
</Grid>
</Window>
Class MainWindow
Public Sub DoStuff()
Dim path = New Uri("c:\test.htm")
AddHandler webBrowser.LoadCompleted, Sub(sender As Object, e As System.Windows.Navigation.NavigationEventArgs)
Dim i = 5
End Sub
webBrowser.Navigate(path)
End Sub
Private Sub Window_Loaded(sender As Object, e As RoutedEventArgs)
DoStuff()
End Sub
End Class
There is no need to use WebBrowser Control to get this issue resolved.
I have also faced this issue and it happens due to the access privileges.
Run your application with Admin privileges and it will be fine.
To debug try running your Visual Studio as Administrator and then test, The DocumentComplete event will be fired.
Update1:
In case of non-admin application if you can manage to get the internet explorer started with Admin privileges then also you can work with it using your non-admin application.
Simple start a Internet Explorer process with admin privileges.
Then you can hook it using this code
For Each IE As InternetExplorer In New SHDocVw.ShellWindows
If IE.FullName.ToLower.Contains("iexplore") And IE.LocationURL <> "" Then
'Capture IE here
End If
Next

How can I force a TabItem to initialize content on load?

[disclaimer: I am new to Visual Basic.]
In a WPF, I have a TabControl containing 2 TabItems:
The first TabItem contains a bunch of URLs.
The second TabItem consists of a DockPanel that contains a cefSharp webView (chromium embedded for .net)
When I click on a url in tab1 it loads a page in the browser contained in tab2... But, it only works if I have initialized the browser first by clicking on tab2.
After doing some searching, it looks like vb.net doesn't initialize the content in a TabItem until it becomes visible. (right?)
So, my question is, how can I force a non-selected tab to initialize its content on load, in the background? (ie. so I don't have to click on the tab or switch to it first)
EDIT:
As requested, here is the relevant code:
The relevant XAML consists of a single DockPanel named "mainBox"
<DockPanel Name="mainBox" Width="Auto" Height="Auto" Background="#afe0ff" />
And here is my "code behind" vb script:
Class MainWindow : Implements ILifeSpanHandler, IRequestHandler
Shared web_view1 As CefSharp.Wpf.WebView
Shared web_view2 As CefSharp.Wpf.WebView
Public Sub init(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Loaded
'This is in a DockPanel created on the xaml named mainBox
' set up tabControl:
Dim browserTabs As New TabControl()
browserTabs.BorderThickness = Nothing
Dim tab1 As New TabItem()
tab1.Header = "My Tab"
Dim tab2 As New TabItem()
tab2.Header = "Browser"
Dim tab1Content As New DockPanel()
Dim tab2Content As New DockPanel()
tab1.Content = tab1Content
tab2.Content = tab2Content
browserTabs.Items.Add(tab1)
browserTabs.Items.Add(tab2)
mainBox.Children.Add(browserTabs)
' set up browsers:
Dim settings As New CefSharp.Settings()
settings.PackLoadingDisabled = True
If CEF.Initialize(settings) Then
web_view1 = New CefSharp.Wpf.WebView()
web_view1.Name = "myTabPage"
web_view1.Address = "http://stackoverflow.com/"
web_view2 = New CefSharp.Wpf.WebView()
web_view2.Name = "browserPage"
web_view2.Address = "https://www.google.com"
web_view2.LifeSpanHandler = Me
web_view2.RequestHandler = Me
AddHandler web_view2.PropertyChanged, AddressOf web2PropChanged
tab1Content.Children.Add(web_view1)
tab2Content.Children.Add(web_view2)
End If
End Sub
End Class
So, in its default state, tab1 is showing at start up -- the browser on tab2 (web_view2) won't initialize until I click its tab or change to its tab via script. Hope this clears it up a bit.
Your code doesn't use Windows Forms' TabPage but perhaps this might still help
As we know, Controls contained in a TabPage are not created until the
tab page is shown, and any data bindings in these controls are not
activated until the tab page is shown. This is by design and you can
call TabPage.Show() method one by one as a workaround.
via MSDN forums
Also, based on the above idea, have you tried the following.
tab2.isEnabled = True
tab1.isEnabled = True
Or:
tab2.Visibility = True
tab1.Visibility = True
Also, the BeginInit Method might help in your situation.
I had the same problem but found it initializes if I use the name of the tabpage and show (tabName1.show()) on the form load code for each page ending with the one I want displayed:
tabPage2.show()
tabPage3.show()
tabPage4.show()
tabPage5.show()
tabPage1.show()
It ripples thru and you never see the pages changing but it works. The other suggestions didn't work for me in Visual Studio 2010.

Mvvm - Cancel changes in Wpf Listbox, vb.net

I have a wpv/mvvm-light/vb.net application with a master/detail view. In this view there is a listbox of clients and a detail view of the client's details where they user can view and edit the customers.
I wanted to add a function where users would be prompted to save changes when a new client is selected in the listbox. If the user chooses yes from the messagebox then save changes and if no then discard changes and return previous selected item back to its original value. I have this all working fine.
My problem is that when the user selects a new client and the messagebox asks them to save changes, the listbox goes out of sync. Meaning that the listbox shows the new client selected but the detail view still shows the previous client. The odd thing is that it works properly on rare occasions.
The following is my view:
<UserControl x:Class="FTC.View.ClientListView"
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:local="clr-namespace:FTC_Application"
mc:Ignorable="d"
d:DesignHeight="400" d:DesignWidth="900">
<ListBox
Grid.Column="1"
Width="350"
Style="{DynamicResource FTC_ListBox}"
ItemTemplate="{DynamicResource FTC_ClientListTemplate}"
ItemContainerStyle="{DynamicResource FTC_ListItem}"
ItemsSource="{Binding ClientViewSource.View}"
SelectedItem="{Binding Path=Selection, Mode=TwoWay}"
/>
<ContentControl DataContext="{Binding Path=Selection, Mode=TwoWay}" >
<!--all the display stuff goes here for the detail view-->
</ContentControl>
</UserControl>
the following is the property in the viewmodel that the selecteditem of the listbox is bound to. It is also the binding for the content control that displays the details.
Public Property Selection As client
Get
Return Me._Selection
End Get
Set(ByVal value As client)
''capture current value of selection
_PreviousClient = _Selection
''If they are the same,
If value Is _PreviousClient Then
Return
End If
' Note that we actually change the value for now.This is necessary because WPF seems to query the
' value after the change. The list box likes to know that the value did change.
If Me._Selection.HasChanges = True And _Selection.HasErrors = False Then
'If HasChangesPrompt(value) = True Then
' ''user rejects saving changes, exit property
' Return
'End If
If FTCMessageBox.Show("Do you want to save your changes", "Unsaved Changes", MessageBoxButton.YesNo, MessageBoxImage.Warning) = MessageBoxResult.No Then
''SELECTION IS CANCELLED
' change the value back, but do so after the UI has finished it's current context operation.
Application.Current.Dispatcher.BeginInvoke(New Action(Sub()
'' revert the current selected item to its original values and reset its HasCHanges tracking
objHelper.CopyProperties(_OriginalClient, _Selection)
_Selection.HasChanges = False
RaisePropertyChanged(ClientSelectedPropertyName)
''continue with listbox selection changing to the new value for selection
_ClientCollectionViewSource.View.MoveCurrentTo(value)
End Sub), DispatcherPriority.Normal, Nothing)
Return
Else
''save changes to database
SaveExecute()
End If
End If
_Selection = value
_Selection.HasChanges = False
RaisePropertyChanged(ClientSelectedPropertyName)
''clone the unchanged version of the current selected client on na original variable
objHelper.CopyProperties(_Selection, _OriginalClient)
End Set
End Property
SO the idea is that if the user does not want to save changes, an original value of the client is copied (using reflection) over the current value, then the ui is updated and the selection continues on to the new value chosen by the user. However, like I said above, the listbox does not reflect this change even though I tired to hard code it with the following line:
''continue with listbox selection changing to the new value for selection
_ClientCollectionViewSource.View.MoveCurrentTo(value)
I got this solution by working customizing the solution posted HERE
can anyone help me figure out why my listbox goes out of sync when this happens.
Thanks in advance
First:
I can't find the real Problem in your solution, but you have definitly - and I repeat - definitly too much code and logic in your Property Setter. Try move it to other methods and validate your implementation of those many ´if else´ blocks.
Second:
The Setter gets only fired when you select a new Item in your Listbox, but you Raise a Property changes for ´ClientSelectedPropertyName´ and not for ´Selection´ as its supposed to be. Move the property changed alsways to the end of your setter.
Try this. I hope it helps :)
So I have a working example that I think follows the MVVM-Light standard. There is a lot going on so I will try to keep it short and precise.
I ended up using EventToCommand bound to the SelectionChanged event with a ListView(instead of listbox). The EventToCommand required new namespace references as is shown below. I then bound the EventToCommand to a RelayCommand in the view model which in turn calls a private sub that handles the client validation and saves/cancels/ and updates the listview seleceditem as required.
For further information, I have a navigation service that is used to navigat between views in my wpf application. I used the MVVM-Light messanger to send a navigationstarting message that is "recieved" by this view model. Then the same client validation functions are performed and naviagtion is canceled/allowed based on user response to the dialog message thrown. I will no include all of hte navigation code unless requested. The following is the code needed to solve my original question.
<UserControl x:Class="FTC.View.ClientListView"
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:local="clr-namespace:FTC_Application"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:cmd="http://www.galasoft.ch/mvvmlight"
mc:Ignorable="d"
d:DesignHeight="400" d:DesignWidth="900">
<ListView
Grid.Column="1"
Width="350"
Style="{DynamicResource FTC_ListView}"
ItemTemplate="{DynamicResource FTC_ClientListTemplate}"
ItemContainerStyle="{DynamicResource FTC_ListViewItem}"
ItemsSource="{Binding ClientViewSource.View}"
SelectedItem="{Binding Path=Selection, Mode=TwoWay}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<cmd:EventToCommand Command="{Binding SelectedItemChangedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ListView>
<ContentControl DataContext="{Binding Path=Selection, Mode=TwoWay}" >
<!-- Display stuff and bound controls go here -->
</ContentControl>
</Grid>
</UserControl>
Then the following is the relevant code (I removed as much code as possible to keep it clear) in my view model:
Imports System.Data
Imports System.ComponentModel
Imports System.Collections.ObjectModel
Imports System.Windows.Threading
Imports GalaSoft.MvvmLight
Imports GalaSoft.MvvmLight.Command
Imports GalaSoft.MvvmLight.Messaging
Imports FTCModel
Imports FTC_Application.FTC.Model
Imports FTC_Application.FTC.View
Imports FTC_Application.FTC.ViewModel
Imports FTC_Application.FTC.MessageBox
Imports FTC_Application.FTC.Helpers
Imports FTC_Application.FTC.MessengerHelper
Namespace FTC.ViewModel
Public Class ClientListViewModel
Inherits ViewModelBase
Implements IDataErrorInfo
#Region "DECLARATIONS"
Public Const ClientCollectionPropertyName As String = "ClientCollection"
Public Const ClientSelectedPropertyName As String = "Selection"
Public Const ClientDetailCollectionPropertyName As String = "ClientDetailCollection"
Public Const ClientPropertyName As String = "Client"
''gets the data from LINQ to ENT Model
Private _Clients As New ObservableCollection(Of client)
''creats holder for the selected item two way binding
Private _Selection As New client
''the following is used to track changes for unding and canceling selection changed
Private _PreviousClient As New client
Private _PreviousOriginalClient As New client
Private _OriginalClient As New client
''Recieves observable collection and provicdes sorting and filtering function
Private _ClientCollectionViewSource As New CollectionViewSource
''RELAY COMMANDS declarations
Private _SaveCommand As RelayCommand
Private _SelectedItemChangedCommand As RelayCommand
''gets the VML for getting the data service
Private vml As ViewModelLocator = TryCast(Application.Current.Resources("Locator"), ViewModelLocator)
''this is a holder for the client data service
Private _ClientAccess As IClientDataService = vml.Client_Service
'' has functions using reflection for copying objects
Dim objHelper As New ObjectHelper
''tracks if client validation is coming from navigation or listview selecteditemchanged
Private bNavigatingFlag As Boolean = False
#End Region
#Region "PROPERTIES"
Public ReadOnly Property ClientViewSource As CollectionViewSource
Get
Return Me._ClientCollectionViewSource
End Get
End Property
Private Property Clients As ObservableCollection(Of client)
Get
Return Me._Clients
End Get
Set(ByVal value As ObservableCollection(Of client))
Me._Clients = value
_Clients = value
RaisePropertyChanged(ClientCollectionPropertyName)
End Set
End Property
Public Property Selection As client
Get
Return Me._Selection
End Get
Set(ByVal value As client)
''capture current value of selection
_PreviousClient = _Selection
objHelper.CopyProperties(_OriginalClient, _PreviousOriginalClient)
''If they are the same,
If value Is _PreviousClient Then
Return
End If
_Selection = value
_Selection.HasChanges = False
RaisePropertyChanged(ClientSelectedPropertyName)
''clone the unchanged version of the current selected client on na original variable
objHelper.CopyProperties(_Selection, _OriginalClient)
End Set
End Property
#End Region
#Region "COMMANDS"
Public ReadOnly Property SelectedItemChangedCommand() As RelayCommand
Get
If _SelectedItemChangedCommand Is Nothing Then
_SelectedItemChangedCommand = New RelayCommand(AddressOf SelectionChangedValidate)
End If
Return _SelectedItemChangedCommand
End Get
End Property
#End Region
#Region "METHODS"
Private Sub SelectionChangedValidate()
''Uses falg to tell if validation request triggered by navigation event or listview selecteditemchanged event
''use previous client for listview event and current client for navigating event
Dim _ClientToValidate As client
If bNavigatingFlag = True Then
_ClientToValidate = _Selection
Else
_ClientToValidate = _PreviousClient
End If
If _ClientToValidate.HasChanges = True And _ClientToValidate.HasErrors = False Then
Dim message = New DialogMessage(_ClientToValidate.chrCompany.ToString + " has been changed." + vbCrLf + "Do you want to save your changes?", AddressOf SavePreviousResponse) With { _
.Button = MessageBoxButton.YesNo, _
.Caption = "Unsaved Changes" _
}
Messenger.[Default].Send(message)
Exit Sub
End If
If _ClientToValidate.HasErrors = True Then
Dim message = New DialogMessage(_ClientToValidate.chrCompany.ToString + " has errors." + vbCrLf + "You must correct these errors before you can continue.", AddressOf HasErrorsResponse) With { _
.Button = MessageBoxButton.OK, _
.Caption = "Validation Error" _
}
Messenger.[Default].Send(message)
Exit Sub
End If
''reset the navigation flag
bNavigatingFlag = False
End Sub
Private Sub SavePreviousResponse(result As MessageBoxResult)
If result = MessageBoxResult.No Then
objHelper.CopyProperties(_PreviousOriginalClient, _PreviousClient)
_PreviousClient.HasChanges = False
Else
''user wants to save changes, save changes to database
SaveExecute()
End If
End Sub
Private Sub HasErrorsResponse(result As MessageBoxResult)
Selection = _PreviousClient
''_ClientCollectionViewSource.View.MoveCurrentTo(_PreviousClient)
End Sub
Private Function HasChangesPrompt(value As client) As Boolean
If FTCMessageBox.Show("Do you want to save your changes", "Unsaved Changes", MessageBoxButton.YesNo, MessageBoxImage.Warning) = MessageBoxResult.No Then
'' change the selected client back to its original value, but do so after the UI has finished its current context operation.
Application.Current.Dispatcher.BeginInvoke(New Action(Sub()
'' revert the current selected item to its original values and reset its HasCHanges tracking
objHelper.CopyProperties(_OriginalClient, _Selection)
_Selection.HasChanges = False
RaisePropertyChanged(ClientSelectedPropertyName)
''continue with listbox selection changing to the new value for selection
_ClientCollectionViewSource.View.MoveCurrentTo(value)
End Sub), DispatcherPriority.Normal, Nothing)
Return True
Else
''user wants to save changes, save changes to database
Return False
SaveExecute()
End If
End Function
#End Region
Public Sub New()
Clients = _ClientAccess.GetClient_All
''Sets the observable collection as the source of the CollectionViewSource
_ClientCollectionViewSource.Source = Clients
If Selection.idClient = 0 Then
Selection = Clients.Item(0)
End If
''register for messages
Messenger.[Default].Register(Of String)(Me, AddressOf HandleMessage)
End Sub
End Class
End Namespace
INXS, you will notice that the selection property setter has way less code/logic. Also, I think each part of the view model is testable and there is no direct coupling between my view and viewmodel. But this is my first WPF/MVVM application so I sitll don't fully grasp all the concepts.
I hope that this can help someone as it took my quite a while to figure it out.

Handling events from user control containing a WPF textbox

In order to take advantage of the spell checking ability of WPF textboxes, I have added one to a user control (with the use of elementhost). This user control is used in various window forms. My current problem is trying to handle keyup events from this textbox but the windows form is unable to "get" any event from the control. I can access the properties of the textbox just fine (i.e. text, length, etc.) but keyboard events don't seem to work.
I have found, however, that the following will bring back events from the WPF textbox:
Public Class MyUserControl
Private _elementHost As New ElementHost
Private _wpfTextbox As New System.Windows.Controls.Textbox
Private Sub MyUserControl_Load(...) Handles Me.Load
Me.Controls.Add(_elementHost)
_elementHost.Dock = DockStyle.Fill
_elementHost.Child = _wpfTextbox
Dim MyEventInfo As EventInfo
Dim MyMethodInfo As MethodInfo
MyMethodInfo = Me.GetType().GetMethod("WPFTextbox_KeyUp")
MyEventInfo = _wpfTextBox.GetType().GetEvent("PreviewKeyUp")
Dim dlg As [Delegate] = [Delegate].CreateDelegate(MyEventInfo.EventHandlerType, Me, MyMethodInfo)
MyEventInfo.AddEventHandler(_wpfTextBox, dlg)
End Sub
Public Sub WPFTextbox_KeyUp(ByVal sender As Object, ByVal e As RoutedEventArgs)
' something goes here
End Sub
End Class
The user control is now able to do something after the PreviewKeyUp event is fired in the WPF textbox. Now, I'm not completely sure how to have the window form containing this user control to work with this.
Im a C# person not VB so please bear with me.. Basically you could assign the event from your Window rather than within your UserControl.. So in the constructor of your Window assign the PreviewKeyUp:
this.myUserContorl.PreviewKeyUp += new System.Windows.Input.KeyEventHandler(WPFTextbox_KeyUp);
then place the event handler in your Window:
private void WPFTextbox_KeyUp(object sender, System.Windows.Input.KeyEventArgs e)
{
}
Incidentally you needn't go through the hassle of capturing the event in your UserControl as you can still access your TextBox within you UserControl directly from your Window (if you make it public), again from your constructor in your Window:
this.myUserContorl.wpfTextbox.PreviewKeyUp += new System.Windows.Input.KeyEventHandler(WPFTextbox_KeyUp);
I imagine it would look like this in VB (at a guess):
AddHandler myUserContorl.wpfTextbox.PreviewKeyUp, AddressOf WPFTextbox_KeyUp
ElementHost has a static method called EnableModelessKeyboardInterop(). Try calling it?
ElementHost.EnableModelessKeyboardInterop();
Read more here
Seems basic, but did you set the KeyPreview to TRUE on your form?

Setting User Control's DataContext from Code-Behind

This should be pretty easy, but it throws VS2008 for a serious loop.
I'm trying out WPF with MVVM, and am a total newbie at it although I've been developing for about 15 years, and have a comp. sci. degree. At the current client, I am required to use VB.Net.
I have renamed my own variables and removed some distractions in the code below, so please forgive me if it's not 100% syntactically perfect! You probably don't really need the code to understand the question, but I'm including it in case it helps.
I have a very simple MainView.xaml file:
<Window x:Class="MyApp.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Main Window" Height="400" Width="800" Name="MainWindow">
<Button Name="Button1">Show Grid</Button>
<StackPanel Name="teststack" Visibility="Hidden"/>
</Window>
I also have a UserControl called DataView that consists of a DataGrid:
<UserControl x:Class="MyApp.Views.DataView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WpfToolkit="http://schemas.microsoft.com/wpf/2008/toolkit" >
<Grid>
<WpfToolkit:DataGrid
ItemsSource="{Binding Path=Entries}" SelectionMode="Extended">
</WpfToolkit:DataGrid>
</Grid>
</UserControl>
The constructor for the DataView usercontrol sets up the DataContext by binding it to a view model, as shown here:
Partial Public Class DataView
Dim dataViewModel As ViewModels.DataViewModel
Public Sub New()
InitializeComponent()
dataViewModel = New ViewModels.DataViewModel
dataViewModel.LoadDataEntries()
DataContext = dataViewModel
End Sub
End Class
The view model for DataView looks like this (there isn't much in ViewModelBase):
Public Class DataViewModel
Inherits ViewModelBase
Public Sub New()
End Sub
Private _entries As ObservableCollection(Of DataEntryViewModel) = New ObservableCollection(Of DataEntryViewModel)
Public ReadOnly Property Entries() As ObservableCollection(Of DataEntryViewModel)
Get
Return _entries
End Get
End Property
Public Sub LoadDataEntries()
Dim dataEntryList As List(Of DataEntry) = DataEntry.LoadDataEntries()
For Each dataentry As Models.DataEntry In dataEntryList
_entries.Add(New DataEntryViewModel(dataentry))
Next
End Sub
End Class
Now, this UserControl works just fine if I instantiate it in XAML. When I run the code, the grid shows up and populates it just fine.
However, the grid takes a long time to load its data, and I want to create this user control programmatically after the button click rather than declaratively instantiating the grid in XAML. I want to instantiate the user control, and insert it as a child of the StackPanel control:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles Button1.Click
Dim dataView As New DataView
teststack.Children.Add(dataView)
End Sub
When I do this, as soon as the Button1_Click finishes, my application locks up, starts eating RAM, and hits the CPU about 50%.
Am I not instantiating my UserControl properly? It all seems to come down to the DataContext assignment in DataEntry's constructor. If I comment that out, the app works as expected (without anything in the grid, of course).
If I move this code block into Button1_Click (basically moving DataEntry's constructor code up a level), the app still fails:
dataViewModel = New ViewModels.DataViewModel
dataViewModel.LoadDataEntries()
dataView.DataContext = dataViewModel
I'm stumped. Can anybody give me some tips on what I could be doing wrong, or even how to debug what infinite loop my app is getting itself into?
Many thanks.
The root cause of your issue appears to be either the raw amount of data you're loading or some inefficiency in how you load that data. Having said that, the reason you're seeing the application lock up is that you're locking the UI thread when loading the data.
I believe that in your first case the data loading has been off loaded onto another thread to load the data. In you second example you're instantiating the control on the UI thread and as a result all the constructor and loading logic is performed on the current thread (the UI thread). If you offload this work onto another thread then you should see similar results to the first example.
I eventually gave up on trying to get the DataContext on the UserControl set during instantiation of the UserControl (either in XAML or code). Now I load up the data and set the DataContext of the UserControl in an event in the UserControl (IsVisibleChanged, I believe). When I instantiate the UserControl in XAML, I have it's Visibility set to Hidden. When Button1 is clicked, I set the UserControl's Visibility to Visible. So the UserControl pops into view, and it loads up its data and DataContext is set. Seems to work, but also seems very kludgey. :-( Thanks for the help, folks!
If it's only a matter of your control taking a long time to populate data, you should populate the control on another thread then add it through a delegate:
Since I'm not too good at writing VB.NET, but here's the C# equivalent:
private void Button1_Click(Object sender, RoutedEventArgs e)
{
Thread thr = new Thread(delegate()
{
DataView dataView = new DataView();
this.Dispatcher.BeginInvoke((Action) delegate()
{
teststack.Children.Add(dataView);
});
});
thr.Start();
}

Resources