How to get ElementHost control, given one of WPF control's content - wpf

I am trying to get a reference to ElementHost control. For example in the below code I need to initially use the “testImage” content of the WPF user control to lunch the event. The WPF control is added at run-time, so does the ElementHost control, so I can’t use the WPF control’s name or ElementHost’s name.
My logic is to get the parent WPF user control of the “testImage”, and then get the parent ElementHost of the WPF’s user control.
But I am having troubles writing it in code. Please advise. Thanks.
<UserControl x:Class="WpfTest”
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="300" Height="300">
<Grid>
<Label FontSize="10" Height="24" Margin="74,16,0,0" Name="testLabel" VerticalAlignment="Top" />
<Image Name="testImage" Stretch="Uniform" HorizontalAlignment="Left" Width="64" Height="81" VerticalAlignment="Top" Margin="8,0,0,0"/>
</Grid>
</UserControl>

Here is some code that might help you. The key points are:
Name the ElementHost when you create it at runtime
Make use the the help function FindVisualChildByName() to search the WPF tree to get the desired control
I Hope this helps!
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Dim ElementHost1 As New System.Windows.Forms.Integration.ElementHost
Dim WpfTest1 As New WindowsApplication1.WPFTest
ElementHost1.Dock = DockStyle.Fill
ElementHost1.Name = "ElementHost1"
ElementHost1.Child = WpfTest1
Me.Controls.Add(ElementHost1)
End Sub
Private Sub GetImageReference_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim ElementHost1 As System.Windows.Forms.Integration.ElementHost = Me.Controls("ElementHost1")
Dim TheGrid As System.Windows.Controls.Grid = CType(ElementHost1.Child, WPFTest).MyGrid
Dim ImageTest As System.Windows.Controls.Image = FindVisualChildByName(TheGrid, "testImage")
Stop
End Sub
Public Function FindVisualChildByName(ByVal parent As System.Windows.DependencyObject, ByVal Name As String) As System.Windows.DependencyObject
For i As Integer = 0 To System.Windows.Media.VisualTreeHelper.GetChildrenCount(parent) - 1
Dim child = System.Windows.Media.VisualTreeHelper.GetChild(parent, i)
Dim controlName As String = child.GetValue(System.Windows.Controls.Control.NameProperty)
If controlName = Name Then
Return child
Else
Dim res = FindVisualChildByName(child, Name)
If Not res Is Nothing Then
Return res
End If
End If
Next
Return Nothing
End Function

Related

How do I change the value of a bound WPF class variable?

I am new to WPF/XAML.
I am making a scrolling marquee using a demo I found online.
It is WinForms using a HostedElement WPF custom User Control.
I was able to setup a class called MyModelView so that I can bind the text value of a textblock to a variable I control (MyTextProperty).
But I can't figure out how to change the variable. My goal is to have a textbox you can enter text into and it will bind to the textblock value and change it while the program is running.
I am not able to do this, which I am trying to do (Change bound variable to TextBox1 value - it is not a valid reference to the variable... what is?):
Private Sub TextBox1_TextChanged(sender As Object, e As EventArgs) Handles TextBox1.TextChanged
MyViewModel.MyTextProperty = TextBox1.text
End Sub
How do I reference that class variable to change the text? I am missing something.
MyViewModel.vb
Imports System.ComponentModel
Public Class MyViewModel
Implements INotifyPropertyChanged
Public Sub New()
Me.myTextValue = "WINNING!!!"
End Sub
Private myTextValue As String = String.Empty
Public Property MyTextProperty() As String
Get
Return Me.myTextValue
End Get
Set(ByVal value As String)
Me.myTextValue = value
NotifyPropertyChanged("MyTextProperty")
End Set
End Property
Public Event PropertyChanged As PropertyChangedEventHandler _
Implements INotifyPropertyChanged.PropertyChanged
Private Sub NotifyPropertyChanged(ByVal propertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
End Class
Scrolling Marque.xaml
<UserControl x:Class="ScrollingMarquee"
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:interopdemo="clr-namespace:InteropDemo"
mc:Ignorable="d"
d:DesignHeight="100" d:DesignWidth="300">
<UserControl.Resources>
<Storyboard x:Key="MarqueeScroll">
<DoubleAnimation RepeatBehavior="Forever"
Storyboard.TargetProperty="(Border.RenderTransform).(TranslateTransform.X)"
Storyboard.TargetName="spMarquee"
From="1500" To="-500"
Duration="0:0:0:7" />
</Storyboard>
</UserControl.Resources>
<Grid>
<Grid.DataContext>
<interopdemo:MyViewModel/>
</Grid.DataContext>
<StackPanel x:Name="spMarquee" Orientation="Horizontal" Width="Auto">
<TextBlock Text="{Binding MyTextProperty}" FontSize="28" VerticalAlignment="Center" Margin="30,0,60,0"/>
<TextBlock Text="Hello Scrolling Text!" Foreground="Firebrick" FontSize="28" VerticalAlignment="Center" Margin="40,0,60,0"/>
<TextBlock Text="Ticker 2000!" Foreground="Red" FontSize="28" FontStyle="Italic" FontWeight="Bold" VerticalAlignment="Center" Margin="50,0,80,0"/>
<StackPanel.RenderTransform>
<TranslateTransform/>
</StackPanel.RenderTransform>
</StackPanel>
</Grid>
</UserControl>
Form1.vb
Imports System.Windows.Media.Animation
Public Class Form1
Private Sub Form1_Load(sender As Object, e As System.EventArgs) Handles Me.Load
Me.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None
Me.WindowState = FormWindowState.Maximized
ElementHost1.Visible = True
Dim storyRunMarquee As Storyboard = ScrollingMarquee1.FindResource("MarqueeScroll")
storyRunMarquee.Begin()
End Sub
End Class
I've tried referencing the variable but can't make it work. The scrolling animation displays as "Winning! Hello Scrolling Text! Ticker 2000!" But I'm trying to change the bound "Winning!" text.
Remove
<Grid.DataContext>
<interopdemo:MyViewModel/>
</Grid.DataContext>
Declare a member variable in the form to hold reference to the view model object:
Private scrollerViewModel As MyViewModel
Instantiate an object of class MyViewModel in Form1_Load and set it as a DataContext for user control.
Private Sub Form1_Load(sender As Object, e As System.EventArgs) Handles Me.Load
Me.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None
Me.WindowState = FormWindowState.Maximized
ElementHost1.Visible = True
scrollerViewModel = New MyViewModel
ScrollingMarquee1.DataContext = scrollerViewModel
Dim storyRunMarquee As Storyboard = ScrollingMarquee1.FindResource("MarqueeScroll")
storyRunMarquee.Begin()
End Sub
Modify property:
Private Sub TextBox1_TextChanged(sender As Object, e As EventArgs) Handles TextBox1.TextChanged
scrollerViewModel.MyTextProperty = TextBox1.text
End Sub
I think you just need to cast the datacontext of ScrollingMarquee1 as MyViewModel
CType(ScrollingMarquee1.DataContext, MyViewModel).MyTextProperty = TextBox1.text

wpf richtextbox in winforms application

I would like to use a WPF RichTextBox in a WinForms project written with VB
I have created the WinForms project with one form and a button
I then added a new project WPF User Control Library placed a WPF RichTextBox on the WPF form
I added ElementHost interoperability to the WinForm with these Imports
Imports System
Imports System.Windows.Forms
Imports System.Windows.Forms.Integration
From here I am lost some of the SO question are 10 to 7 years old the MS tutorial is not much help
Code from WPF Form
Public Class UserControl1
ReadOnly rtbWPF As New UserControl
ElementHost
wpfwindow.Show
End Class
I did not post the XAML code NOT sure how to do that
So the question what to do next to link the WPF form with the RTB to the WinForms form?
I would like to load data from a SQLite DB into the WPF RichTextBox and save the text entered in the RTB into the DB
This answer is meant to expand on #KyleWang wonderful answer
One BIG issue with Vectors choice of the WPF RichTextBox is There is no Text property in the WPF RichTextBox control. Here is one way to get all of the text out. That said I would IMHO would suggest using the WPF Plain TextBox control
Vector also commented on how to hide the HotReload in the title bar
Tools > Options > Debugging > General > un-check Enable UI Debugging Tools for XAML
OK Code Below hope this is helpful if you decide to use a WPF control in WinForms for spell checking
Public Class frmStart
Dim rtb As Windows.Controls.RichTextBox = New Windows.Controls.RichTextBox()
Dim tb As Windows.Controls.TextBox = New Windows.Controls.TextBox()
Dim str As String = " "
Private Sub frmStart_Load(sender As Object, e As EventArgs) Handles MyBase.Load
ElementHost1.Child = rtb
rtb.SpellCheck.IsEnabled = True
ElementHost2.Child = tb
tb.SpellCheck.IsEnabled = True
If str.Length < 100 Then
rtb.VerticalScrollBarVisibility = Windows.Controls.ScrollBarVisibility.Visible
End If
End Sub
Private Sub btnAdd_Click(sender As Object, e As EventArgs) Handles btnAdd.Click
str = "Plain WPF TxtBox"
tb.Text = str
rtb.AppendText("Heree is som mispelled txt se if the dictioary wrks more nonsense to see the scroll bar's will this word wrapp or is that rapp")
End Sub
Private Sub btnGet_Click(sender As Object, e As EventArgs) Handles btnGet.Click
Dim elementHost = ElementHost1
Dim wpfRichText = CType(elementHost.Child, Windows.Controls.RichTextBox)
Dim range As Windows.Documents.TextRange = New Windows.Documents.TextRange(rtb.Document.ContentStart, rtb.Document.ContentEnd)
Dim allText As String = range.Text
tbMsg.Text = allText.ToString
End Sub
Private Sub btnGTB_Click(sender As Object, e As EventArgs) Handles btnGTB.Click
Dim elementHost = ElementHost2
Dim text = tb.Text
tbMsg.Text = text.ToString
End Sub
To host a WPF control in Winforms, you can refer to the following two ways.
First, both of them need to add a ElementHost control into form.
Solution A:
Directly declare the wpf controls (using Windows.Controls)
Dim rtb As Windows.Controls.RichTextBox = New Windows.Controls.RichTextBox()
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
rtb.SpellCheck.IsEnabled = True
ElementHost1.Child = rtb
End Sub
Solution B:
Create a new User Control(WPF) and edit the content in "UserControl1.xaml" as follows.
<Grid>
<RichTextBox x:Name="richtextbox" Foreground="Black" FontSize="24" Margin="0"></RichTextBox>
<RichTextBox SpellCheck.IsEnabled="True" />
</Grid>
Then modify the code in 'form1.vb'
Private uc As UserControl1 = New UserControl1()
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
ElementHost1.Child = uc
End Sub

Closing just one expander within another expander in WPF/ VB.NET

First post here so sorry if I mess something up/forget something.
I'm working in Visual Studio 2010 with a WPF application and am using VB.NET.
So I have a parent expander that contains a grid with two controls: another expander and another grid. I have written some code behind to cause each expander to become invisible when it is collapsed or closed (among a few other things), and to change color/ become visible when it is expanded/opened. I also have a few buttons placed in other areas that accomplish the same tasks. My problem is that when I collapse the 2nd expander, the 1st (parent) expander also closes/ becomes invisible. However, a button that is used to collapse the 2nd expander works perfectly. Here is my relevant code (hopefully I'm formatting this right):
XAML
<Expander Name="Expander1" Visibility="Hidden" >
<Grid >
<Grid.RowDefinitions >
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Expander Name="Expander2" Visibility="Hidden" >
<Content ...>
</Expander>
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="20" />
</Grid.RowDefinitions>
<Content... />
</Grid>
</Grid>
</Expander>
VB.NET
Private Sub Expander2_Expanded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles Expander2.Expanded
Expander2.Background = Brushes.PaleTurquoise
Expander2.BorderBrush = Brushes.Black
End Sub
Private Sub Expander2_Collapsed(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles Expander2.Collapsed
Expander2.IsExpanded = False
Expander2.Background = Brushes.Transparent
Expander2.BorderBrush = Brushes.Transparent
Expander2.Visibility = Windows.Visibility.Visible
ButtonA7.Visibility = Windows.Visibility.Visible
Expander1.IsExpanded = True
Expander1.Background = Brushes.PaleTurquoise
Expander1.BorderBrush = Brushes.Black
Expander1.Visibility = Windows.Visibility.Visible
End Sub
Private Sub Expander1_Expanded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles Expander1.Expanded
Expander1.Background = Brushes.PaleTurquoise
Expander1.BorderBrush = Brushes.Black
End Sub
Private Sub Expander1_Collapsed(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles Expander1.Collapsed
Expander1.Background = Brushes.Transparent
Expander1.BorderBrush = Brushes.Transparent
Expander1.Visibility = Windows.Visibility.Hidden
ButtonA7.Visibility = Windows.Visibility.Visible
End Sub
Dont worry about all the buttons in the code, the buttons all work fine. In fact, one button is suppose to do the EXACT same thing as collapsing the expander, and it works properly. I just need the same thing to happen when you click on the actual expander to collapse it. Here is the code for the button so you see it is the same:
Private Sub Button_Click_2(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
Expander2.IsExpanded = False
Expander2.Background = Brushes.Transparent
Expander2.BorderBrush = Brushes.Transparent
Expander2.Visibility = Windows.Visibility.Visible
Expander1.IsExpanded = True
Expander1.Background = Brushes.PaleTurquoise
Expander1.BorderBrush = Brushes.Black
Expander1.Visibility = Windows.Visibility.Visible
ButtonA7.Visibility = Windows.Visibility.Visible
End Sub
Thank you so much for any help, I really appreciate it!
EDIT: Alternatively, if there is an easy way (I am VERY new to WPF... ~1week) to hide/ get rid of the header, that could work too. But I would prefer it the other way where I tried before, if possible. Thanks!
Expander.Collapsed Event has routing strategy Bubbling. Mark event as handled before it reaches Expander1 and all should be okay.
Private Sub Expander2_Collapsed(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles Expander2.Collapsed
...
e.Handled = True
End Sub

Update LineGraph Every Second

Hi I'm trying to update a line chart every second (from the wpf data visualisation toolkit). The following code works for a PieChart:
VB:
Private Sub Window_Loaded(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded
' DispatcherTimer setup
Dim dt As DispatcherTimer = New DispatcherTimer()
AddHandler dt.Tick, AddressOf dispatcherTimer_Tick
dt.Interval = New TimeSpan(0, 0, 1)
dt.Start()
End Sub
Public Sub dispatcherTimer_Tick(ByVal sender As Object, ByVal e As EventArgs)
UpadateChartData()
Chart1.DataContext = ""
Chart1.DataContext = ChartData
End Sub
Public Sub UpadateChartData()
Dim r As Integer = GetRandom(1, 10)
ChartData.Add(New ChartData() With {.Name = r.ToString, .Votes = r})
End Sub
XAML:
<chartingToolkit:Chart Name="Chart1" Title="Chart1" Width="400" Height="400" >
<chartingToolkit:LineSeries ItemsSource="{Binding}" IndependentValueBinding="{Binding Path=Name}" DependentValueBinding="{Binding Path=Votes}" />
</chartingToolkit:Chart>
But I get this error when I use LineSeries (or any other type of chart):
Cannot modify the logical children for this node at this time because a tree walk is in progress.
Can anyone tell me why I'm getting the error or suggest an alternative approach?
Thanks for any help!
It indeed appears to be a bug in the toolkit, as ColinE mentioned.
I would suggest to take a look at Dynamic Data Display, which really facilitates these kind of things and makes it very easy. They provide a few new objects like ObservableDataSource<T>, and on this you can call source.AppendAsync(Dispatcher, T) which will update the graph.

Update UI when property is changed

I am quite new to WPF and I am confused about how Data Bindings should behave.
I have created a class that has 1 property ("status") and three methods that are responsible for changing the status. This class implements the INotifyPropertyChanged interface so that I am able to notify the calling code when the Status changes.
The class looks like this:
Public Class StreetLight
Implements System.ComponentModel.INotifyPropertyChanged
Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
Private _status As String
Public Property Status As String
Get
Return _status
End Get
Set(ByVal value As String)
_status = value
RaiseEvent PropertyChanged(Me, New System.ComponentModel.PropertyChangedEventArgs("Satus"))
End Set
End Property
Public Sub New()
_status = "unknown"
End Sub
Public Sub Red()
Status = "Red"
End Sub
Public Sub Yellow()
Status = "Yellow"
End Sub
Public Sub Green()
Status = "Green"
End Sub
End Class
I have created a WPF User Control to represent this class.
This User Control is bound to an instance the StreetLight class. It displays the status of the StreetLight and allows the user to change the status using buttons:
<UserControl x:Class="StreetLightUC"
x:Name="StreetLightUC"
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:twpf="clr-namespace:TryingWPF"
mc:Ignorable="d"
d:DesignHeight="30" d:DesignWidth="360">
<UserControl.Resources>
<twpf:StreetLight x:Key="theLight" PropertyChanged="theLight_PropertyChanged" />
</UserControl.Resources>
<StackPanel x:Name="StreetLightContent" Orientation="Horizontal">
<Label Width="100" HorizontalAlignment="Left">Street Light _Status</Label>
<Label x:Name="streetLightValue" Width="120" HorizontalAlignment="Right" Content="{Binding Path=Status, Mode=OneWay}"></Label>
<Button x:Name="Red" Click="TurnRed" Width="60">Turn Red</Button>
<Button x:Name="Green" Click="TurnGreen" Width="60">Turn Green</Button>
</StackPanel>
</UserControl>
My problem is that even when the status is changed for theLight, it is not updated in the Label that is bound to the Status property unless I create a new StreetLight and set the DataContext to this new instance in the "StreetLight_PropertyChanged" event that handles the PropertyChagned event for the "theLight"
Like so:
Public Class StreetLightUC
Public Sub New()
InitializeComponent()
End Sub
Private Sub TurnRed(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
Dim light As StreetLight= CType(FindResource("theLight"), StreetLight)
light.Red()
End Sub
Private Sub TurnGreen(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
Dim light As StreetLight = CType(FindResource("theLight"), StreetLight)
light.Unlock()
End Sub
Private Sub theLight_PropertyChanged(ByVal sender As System.Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs)
Dim light As StreetLight = CType(sender, StreetLight )
Dim newLight As New StreetLight
newLight.Status = light.Status
StreetLightContent.DataContext = newLight
End Sub
End Class
Am I doing something wrong?
It doesn't seem like I should have to create a new instance of the class to display the updated status-property when this property is change....
Thanks,
-Frinny
You have a typo ("Satus" instead of "Status"):
RaiseEvent PropertyChanged(Me, New System.ComponentModel.PropertyChangedEventArgs("Satus"))
Should be:
RaiseEvent PropertyChanged(Me, New System.ComponentModel.PropertyChangedEventArgs("Status"))
With this typo, the binding doesn't see that "Status" has changed, and never updates. If you correct this, the PropertyChanged event will correctly reflect that "Status" has changed.

Resources