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.
Related
I have a WPF app with a FlowDocumentReader control and when I click the search button to search the text, the text box doesn't match the theme of the app. The text does, but the background of the text box doesn't. So if I am using a dark theme, the text I type into the text box is white (which is correct), but the text box background is also white (incorrect, making text not legible).
Does anyone know how to fix this? I tried applying a style but I don't know which component to target.
Wow, Microsoft really does not make this easy.
My Process
I tried the easy trick of adding a Style TargeType="TextBox" to FlowDocumentReader.Resources, but that doesn't work.
I tried doing things the "right" way and overriding FlowDocumentReader's ControlTemplate, but the TextBox in question isn't even part of the ControlTemaplte! Instead, there's a Border named PART_FindToolBarHost. The TextBox we want is added as a child to PART_FindToolBarHost in code- but only after the user has clicked the "find" button (the one with the magnifying glass icon). You can see this for yourself by looking at the control's source code.
With no more XAML-only ideas, I had to resort to using code. We need to somehow get a reference to the TextBox being created, and I can't think of any better way than to manually search the visual tree for it. This is complicated by the fact that the TextBox only exists once the find command has been executed.
Specifically, the find button in FlowDocumentReader binds to ApplicationCommands.Find. I tried adding a CommandBinding for that command to FlowDocumentReader so I could use it as a trigger to retrieve the TextBox. Unfortunately, adding such a CommandBinding somehow breaks the built-in functionality and prevents the TextBox from being generated at all. This breaks even if you set e.Handled = False.
Luckily, though, FlowDocumentReader exposes an OnFindCommand- except, of course, it's a Protected method. So I finally gave in and decided to inherit FlowDocumentReader. OnFindCommand works as a reliable trigger, but it turns out the TextBox isn't created until after the sub finishes. I was forced to use Dispatcher.BeginInvoke in order to schedule a method to run after the TextBox was actually added.
Using OnFindCommand as a trigger, I was finally able to reliably get a reference to the "find" TextBox, which is actually named FindTextBox.
Now that I can get a reference, we can apply our own Style. Except: FindTextBox already has a Style, so unless we want to override it, we're going to have to merge the two Styles. There's no publicly-accessible method for this (even though WPF does this internally in places), but luckily I already had some code for this.
The Working Code
First, a Module with the helper methods I used:
FindVisualChild is used to loop through the visual tree and get a reference to FindTextBox.
MergeStyles is used to combine the existing Style with the Style we supply, once we have that reference.
Module OtherMethods
<Extension()>
Public Function FindVisualChild(obj As DependencyObject, Name As String) As FrameworkElement
For i As Integer = 0 To VisualTreeHelper.GetChildrenCount(obj) - 1
Dim ChildObj As DependencyObject = VisualTreeHelper.GetChild(obj, i)
If TypeOf ChildObj Is FrameworkElement AndAlso DirectCast(ChildObj, FrameworkElement).Name = Name Then Return ChildObj
ChildObj = FindVisualChild(ChildObj, Name)
If ChildObj IsNot Nothing Then Return ChildObj
Next
Return Nothing
End Function
Public Function MergeStyles(ByVal style1 As Style, ByVal style2 As Style) As Style
Dim R As New Style
If style1 Is Nothing Then Throw New ArgumentNullException("style1")
If style2 Is Nothing Then Throw New ArgumentNullException("style2")
If style2.BasedOn IsNot Nothing Then style1 = MergeStyles(style1, style2.BasedOn)
For Each currentSetter As SetterBase In style1.Setters
R.Setters.Add(currentSetter)
Next
For Each currentTrigger As TriggerBase In style1.Triggers
R.Triggers.Add(currentTrigger)
Next
For Each key As Object In style1.Resources.Keys
R.Resources(key) = style1.Resources(key)
Next
For Each currentSetter As SetterBase In style2.Setters
R.Setters.Add(currentSetter)
Next
For Each currentTrigger As TriggerBase In style2.Triggers
R.Triggers.Add(currentTrigger)
Next
For Each key As Object In style2.Resources.Keys
R.Resources(key) = style2.Resources(key)
Next
Return R
End Function
End Module
Then, there's StyleableFlowDocumentReader, which is what I named my extended control that inherits FlowDocumentReader:
Public Class StyleableFlowDocumentReader
Inherits FlowDocumentReader
Protected Overrides Sub OnFindCommand()
MyBase.OnFindCommand()
Dispatcher.BeginInvoke(Sub() GetFindTextBox(), DispatcherPriority.Render)
End Sub
Private Sub GetFindTextBox()
findTextBox = Me.FindVisualChild("FindTextBox")
ApplyFindTextBoxStyle()
End Sub
Private Sub ApplyFindTextBoxStyle()
If findTextBox IsNot Nothing Then
If findTextBox.Style IsNot Nothing AndAlso FindTextBoxStyle IsNot Nothing Then
findTextBox.Style = MergeStyles(findTextBox.Style, FindTextBoxStyle)
Else
findTextBox.Style = If(FindTextBoxStyle, findTextBox.Style)
End If
End If
End Sub
Private findTextBox As TextBox
Public Property FindTextBoxStyle As Style
Get
Return GetValue(FindTextBoxStyleProperty)
End Get
Set(ByVal value As Style)
SetValue(FindTextBoxStyleProperty, value)
End Set
End Property
Public Shared ReadOnly FindTextBoxStyleProperty As DependencyProperty =
DependencyProperty.Register("FindTextBoxStyle",
GetType(Style), GetType(StyleableFlowDocumentReader),
New PropertyMetadata(Nothing, Sub(d, e) DirectCast(d, StyleableFlowDocumentReader).ApplyFindTextBoxStyle()))
End Class
And then, finally, a usage example:
<local:StyleableFlowDocumentReader x:Name="Reader">
<local:StyleableFlowDocumentReader.FindTextBoxStyle>
<Style TargetType="TextBox">
<Setter Property="Foreground" Value="Blue"/>
</Style>
</local:StyleableFlowDocumentReader.FindTextBoxStyle>
<FlowDocument/>
</local:StyleableFlowDocumentReader>
I have a navigation control that is displayed in multiple pages. So, I have no way to identify the exact class name during design time. Now, when user navigates to different page, I want to hide the current page. Basically, a typical menubar behaviour. I am able to get the outermost element as a dependency object using the code below.
Private Function GetTopLevelControl(ByVal control As DependencyObject) As DependencyObject
Dim tmp As New DependencyObject
tmp = control
Dim parent As New DependencyObject
parent = Nothing
While Not VisualTreeHelper.GetParent(tmp) Is Nothing
parent = VisualTreeHelper.GetParent(tmp)
End While
Return parent
End Function
Now, on the mouse down event, I am trying to write code to hide this parent object.
Private Sub Menu_Nomination_MouseDown(sender As Object, e As MouseButtonEventArgs)
Dim surveySearchPage As New SurveySearch
surveySearchPage.Show()
Dim parentControl As DependencyObject
parentControl = GetTopLevelControl(Me)
parentControl
End Sub
Problem is parentControl object has no hide or close property at all. So, I am currently stuck in trying to close the page.
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.
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.
I am from VB.Net WinForms comming. Now I wanted to write a small app in WPF, listing some files in a datagridview. I used WPF's DataGrid, created some Columns. And then failed to add my rows.
Please, can you help me to select the right way to get my filenames, state-text and thumbnails added to the DataGrid Row?
In VB.Net WinForms I can add a row like this:
Datagridview1.Rows.add(Myvalue, "RowStateText", "Hello World", MyDate)
In WPF's DataGrid I can add
DataGrid1.Items.Add(New DataGridRow())
But how to fill my DataGridRow?
Private Sub AddFilesAndFolders(ByVal Base As IO.DirectoryInfo, ByRef dgv As DataGrid)
'For Each di As IO.DirectoryInfo In Base.GetDirectories
' Call AddFilesAndFolders(di, dgv)
'Next
Dim item As DataGridRow
For Each fi As IO.FileInfo In Base.GetFiles
item = New DataGridRow'<-- test 1 (row is added but empty)
Dim di As New MyFileInfo'<-- test 2 (my own class with public members, but how to add as row with declared columns?)
di.FileName = fi.FullName
di.FileDate = fi.LastAccessTime
item.Item = fi.FullName
dgv.Items.Add(di)
Next
End Sub
Hi: you should set an ItemsSource instead of adding items manually. If the columns are set up correctly then it will just 'work'!
dbv.ItemsSource = Base.GetFiles
or
dbv.ItemsSource = CreateMyFileInfos(Base.GetFiles)
If you have any more problems, please post back here.
Edit: on second inspection it looks like you may want to be doing it recursively. In which case your AddFilesAndFolders could instead be CreateFilesAndFolders, which would return a collection of FileInfo/MyFileInfo objects, merged with the collections produced by the child folders recursively; then bind the whole list returned from the first call, to the grid.
Hope that helps!
WPF is a mindset change, you need to get away from the Winforms way of thinking.
Ultimately you need to set the ItemsSource to an IEnumerable, preferably a ObservableCollection.
The quickest way to get started would be to put the ObservableCollection as a public property in your code-behind file:
public ObservableCollection<DirectoryInfo> files { get;set; }
Then in the constructor or a Load event on the Window, populate the collection with your data and then add to the Xaml declaration for your DataGrid:
ItemsSource = "{Binding Path=files}"
EDIT:
I tried this out using the DirectoryInfo class, in my code behind I added:
public ObservableCollection<DirectoryInfo> Dir = new ObservableCollection<DirectoryInfo>();
public Window1()
{
InitializeComponent();
Dir.Add(new DirectoryInfo("c:\\"));
Dir.Add(new DirectoryInfo("c:\\temp\\"));
dataGrid1.ItemsSource = Dir;
}
For some reason this was not working using the Databinding via Xaml, but I did not try very hard to get it to work.