I can't use Window.Show despite of creating new Window instance - wpf

here's the problem:
In my WPF application I used to load/parse my .xaml files using XamlReader.Load Method to open a window in my application.
Codefragment of my function which return the window:
Dim win As New Window()
Dim myObject As Object
Dim xml As XmlReader = XmlReader.Create("mysample.xaml")
myObject = System.Windows.Markup.XamlReader.Load(xml)
win = CType(myObject, Window)
Return win
I use this to display all my different windows the user wants to see.
I open the window with win.Show and close it, when user switch to another window with win.Close. It works well!
Now to increase the performance I plan to do all the XAMLReader.Load at Application Start and store the information into a Dictionary:
Private Shared windict As Dictionary(Of String, Object)
Public Shared Sub ConvertXAMLToWindow(ByVal formName As String)
windict = New Dictionary(Of String, Object)
Dim myObject As Object
Dim xml As XmlReader = XmlReader.Create(formName)
myObject = System.Windows.Markup.XamlReader.Load(xml)
windict.Add(formName, myObject)
End Sub
Then I want to use that information when calling windows:
If windict.ContainsKey(formName) Then
Dim win As New Window()
Dim myObject As Object
myObject = windict(formName)
win = CType(myObject, Window)
Return win
End If
Now
This works well, but when I use win.Close to close my window I get an error when trying to open it again with win.Show, although I create an new instance of Window?
System.InvalidOperationException
Cannot set Visibility or call Show, ShowDialog... after a Window has
closed.
But it works when I don't use the Dictionary Method but the XAMLReader.Load directly - any ideas whats going on ? Somehow the window I get by returning XamlReader.Load seems different than the stored information from the dict?? Am I missing somehting? Thanks in advance!

You could use Hide() instead of Close()
Hide hides the Form, so instead of disposing of the form (and its controls) you make it invisible. Show will make it visible again.
Be careful though, the form in the dictionary will still hold the state from the previous time it was used.

Related

Manipulate visibility by using x:Name=" as string from code

Is it possible to manipulate visibility and other options of element by using x:Name=" as string in VB.Net + WPF?
Things i tried?
Dim w As MainWindow = Application.Current.Windows(0)
'Dim nameOfControl As String = ("buttonClose")
w.[nameOfControl].Visibility = Visibility.Visible
And outpus is as i expected it to be, nameOfControl is not memeber of main window
My final result should be to manipulate(visualy) all gui elements based on database information.
(Turns out OP needs to look up arbitrary controls by string name in a loop -- which is ideally done via MVVM and a redesign, but for now the answer is walking the visual tree).
You've already pretty much got it.
Dim w As MainWindow = Application.Current.Windows(0)
w.buttonClose.Visibility = Visibility.Collapsed
When you put the x:Name attribute on a control, WPF gives the parent class (MainWindow in this case) a Friend property for the control that has that name. Friend (C# calls it internal) means any code in the same assembly has access to it. w is a reference to your MainWindow class, so there it is.
Therefore, assuming this code is in the same assembly as MainWindow, that should work.
Edit2: Fixed a bug that caused only the first child of matching type to be returned.
Edit: After looking into this more, it seems there are some substantial differences with extension methods in C# and VB.Net. I have made some changes to hopefully account for those changes.
The following method will get all children of the given visual (Window is a Visual):
Imports System.Collections.Generic;
Imports System.Windows;
Imports System.Windows.Media;
Module VisualExtensions
<System.Runtime.CompilerServices.Extension>
Public Function GetVisualChildren(Of T As Visual)(parent As DependencyObject) As IEnumerable(Of T)
Dim child As T = Nothing
Dim numVisuals As Integer = VisualTreeHelper.GetChildrenCount(parent)
For i As Integer = 0 To numVisuals - 1
Dim v As Visual = DirectCast(VisualTreeHelper.GetChild(parent, i), Visual)
child = TryCast(v, T)
If v IsNot Nothing Then
For Each item As var In GetVisualChildren(Of T)(v)
yield Return item
Next
End If
If child IsNot Nothing Then
yield Return child
End If
Next
End Function
End Module
This extension method must be in a Module, like shown above.
You can get a child with a specific name like so:
Dim window = Application.Current.Windows(0)
Dim visuals = window.GetVisualChildren(Of FrameworkElement)()
Dim nameOfControl = "NameOfControl"
Dim child = visuals.OfType(Of FrameworkElement)().FirstOrDefault(Function(x) x.Name = nameOfControl)
child.Visiablity = Visibility.Collapsed
FrameworkElement can be replaced if you know what the type of the child control is. Note that if there are multiple children with the same name, only one of those children will be returned. Use Where() instead of FirstOrDefault() if you want to get them all.
Disclaimer: I am a C# programmer, all of this VB.Net code was converted from C# to VB using telerik's converter found here. There may be syntax or other errors in this code.

To set the owner property for WPF window

I am Working on creating addin for Autodesk Inventor, I have class lib project in that I will show a wpf window on button click, that works great. However, I could not set the owner property for my window.. In my research I came to know that we need to get the parent window object..
If you're not able to get parent window, you can try setting the parent using window handle.
I'm not familiar with Autodesk Inventor and how you create plugin for the application so I don't know if you can get window handle but I guess you could know process id or window caption/title or some other information that can help you get the parent window handle (you should Google how to get window handle). Once you have handle of parent window you can set it as an owner of your window using WindowInteropHelper.
Here's just a sample how to use WindowInteropHelper:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
IntPtr parentWindowHandler = IntPtr.Zero;
// I'll just look for notepad window so I can demonstrate (remember to run notepad before running this sample code :))
foreach (Process pList in Process.GetProcesses())
{
if (pList.MainWindowTitle.Contains("Notepad"))
{
parentWindowHandler = pList.MainWindowHandle;
break;
}
}
var interop = new WindowInteropHelper(this);
interop.EnsureHandle();
// this is it
interop.Owner = parentWindowHandler;
// i'll use this to check if owner is set
// if it's set MainWindow will be shown at the center of notepad window
WindowStartupLocation = System.Windows.WindowStartupLocation.CenterOwner;
}
}
I'm just reiterating the example that user1018735 gave. I develop Inventor Add-ins so I modified the code above to make sure I am always working with the correct Inventor session since multiple instances of Inventor can be open at once. I do this by passing my already known application object to my form through the _App parameter; then since the process MainWindowTitle is always the same as the applications caption I match the two.
I'm running this in a WPF Class Library # VB.Net 4.5.1.
Here is a peek at my code that works in Inventor 2014...
Public Sub New(ByVal _App As Inventor.Application)
'This call is required by the designer.
InitializeComponent()
'Find the Inventor Window Handle.
Dim InvWndHnd As IntPtr = IntPtr.Zero
'Search the process list for the matching Inventor application.
For Each pList As Process In Process.GetProcesses()
If pList.MainWindowTitle.Contains(_App.Caption) Then
InvWndHnd = pList.MainWindowHandle
Exit For
End If
Next
Dim InvWndIrp = New WindowInteropHelper(Me)
InvWndIrp.EnsureHandle()
InvWndIrp.Owner = InvWndHnd
...
// Create a window and make this window its owner
Window ownedWindow = new Window();
ownedWindow.Owner = this;
ownedWindow.Show();

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.

WPF - programmatically referencing image resource in codebehind of Window with no XAML

Consider a class that inherits from System.Windows.Window like so:
Public Class MyWindow
Inherits Window
Private _root As Grid
Public Sub New()
MyBase.New()
_root = New Grid
Me.AddChild(_root)
End Sub
Private Sub Me_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
Dim image As New Image
Dim bitmapImage As New BitmapImage(New Uri("Assets/MyImage.png", UriKind.Relative))
image.Source = bitmapImage
image.Width = 100
image.Height = 100
image.Stretch = Stretch.Fill
_root.Children.Add(image)
End Sub
End Class
Let it be part of a WPF Windows Application that has the following module as its startup object:
Module MyModule
Sub main()
Dim myApplication As New Application
myApplication.Run(New MyWindow)
End Sub
End Module
The window gets displayed, but the image does not. When inserting the exact same image loading code in the Loaded event of a VS default MainWindow class (MainWindow.xaml.vb), the image shows up as expected. MyImage.png has 'Build Action' set to 'Resource' in both cases. What am I missing here?
Edit:
I learned that such references in codebehind must be specified using the Pack URI scheme, so replacing the Uri code with
New Uri("pack://application:,,,/Assets/MyImage.png")
will make it work. The problem was that the relative Uri was interpreted as 'file system absolute' (despite having specified UriKind.Relative), and the image location got resolved to C:\Assets\MyImage.png.
But that doesn't answer the underlying question: Why does
New Uri("Assets/MyImage.png", UriKind.Relative)
work when used in the codebehind of the standard MainWindow class (which also inherits Window, but additionally has some associated XAML), but not in a 'bareboned' descendant of Window like the MyWindow class above (defined in code only)?
Apparently, the Uri was interpreted as absolute - even though I specified it as UriKind.Relative.
When replacing with
Dim bitmapImage As New BitmapImage(New Uri("pack://application:,,,/Assets/MyImage.png"))
it works.

Printing in WPF

I've created some windows in wpf that I need to print to one xps document. Each window opens, loads the relevant data and then immediately closes. Currently I use the below code to create the xps:
Using doc = New XpsDocument(TempLoc, FileAccess.Write)
Dim writer = XpsDocument.CreateXpsDocumentWriter(doc)
Dim collator = writer.CreateVisualsCollator()
Dim Window1 As Window1 = New Window1()
Window1.ShowDialog()
Dim Window2 As Window2 = New Window2()
Window2.ShowDialog()
Dim WindowX As WindowX = New WindowX()
WindowX.ShowDialog()
collator.BeginBatchWrite()
collator.Write(Window1)
collator.Write(Window2)
collator.Write(WindowX)
collator.EndBatchWrite()
End Using
Dim doc2 = New XpsDocument(TempLoc, FileAccess.Read)
Dim seq = doc2.GetFixedDocumentSequence()
Dim window = New Window()
window.Content = New DocumentViewer() With {.Document = seq}
window.ShowDialog()
doc2.Close()
However the trouble with this approach is that the area printed varies between machines - I assume this is due to the local screen size being used etc.
Is it possible to make the program print the full window independent of the computer its on by modifying this code? Alternativly is there a better way to approach this problem?
Thanks for any help
I print with FixedDocuments by either appending or adding placed UIElements. I posted the full source code to my helper class here. You may find that breaking it down by UIElements gives you much better control over your exact printed output, though yes, it requires you to separate printing code instead of just emitting your window.
I've used this helper class to create some very nicely formated multi-page reports that always come out the same on every computer.

Resources