Use WPF and inline XAML in your Fox, Excel or VB6 applications


My prior post showed how to create XAML WPF and put it on your Winform App. We can go one step further: add XAML to a UserControl, which could then be made into an ActiveX control, which could be hosted by Fox, VB6, or Excel.


 


 


Start Visual Studio 2008 (as admin on Vista!!: (to register for COM interop, you need admin privileges))


Choose File->New Project->Visual Basic->Windows->Class Library. Call it WPFClass


 


From the Solution Explorer, delete Class1.vb, then right click on the WPFClass project (not the WPFClass solution), choose Add New Item, choose Windows Forms->User Control, call it WPFControl.vb


From Project->Properties->Application, make sure the Root Namespace is WPFClass and not ClassLibrary1. (If you don’t the ProgId for the control will be ClassLibrary1. WPFClass). Now the ProgId will be WPFClass. WPFControl


Also, choose Project->Properties->Compile->Register for COM Interop


 


You just created a control with a design surface. 


 


Add references to


 


PresentationCore


PresentationFrameWork


WindowsBase


WindowsFormsIntegration


 


 


Choose View->Code. Paste in the Sample Code below, then hit F5. The UserControl is automatically instantiated and shown inside the UserControl TestContainer. The sample code is basically the same as the prior post, except that it is a UserControl and has another method called SetXAML, which allows a client to change the XAML on the fly of the instantiated control.


 


Change Project->Properties->Debug->Start Action->Start External Program and navigate to Excel.Exe or Visual Foxpro.(VFP9.EXE)


 


In Excel, (2003 or 2007) choose Developer->Visual Basic (2007) or Tools->Macros->VB Editor (2003). Same hotkey for both: Alt-F11. In the VB Editor, insert a User Form. Right click on the Toolbox-.Additional Controls. Check WPFClass.WPFControl. Drag it onto the form.


 


This Fox client code dynamically generates new XAML code for the control to use with inline XAML when you hit the button. It creates a listbox with the names of some pictures or movies. It has a single MediaElement which is databound to the ListBox. As you click on an item, it displays. Notice how we can use styles for the listbox by adding a Style resource to the Canvas that applies to all  ListBoxItems for that Canvas.                                                  


Make sure you change the path to some pictures/movies


 


Try creating an error in your Fox XAML and you’ll see a MessageBox showing the error.


 


<FoxClient Code>


PUBLIC ox


ox=CREATEOBJECT(“myform”)


ox.show


 


 


DEFINE CLASS myform AS form


      left=100


      allowoutput=.f.


      width=900


      height=700


      ADD OBJECT btn as commandbutton WITH caption=”Change XAML”


      PROCEDURE Init


            this.AddObject(“ocWPF”,”OleControl”,”WPFClass.WPFControl”)


            this.ocWPF.visible=1


            this.ocWPF.Top=20


            this.ocWPF.width=thisform.Width-100


            this.ocWPF.height=thisform.Height-100


      PROCEDURE btn.Click


      *Use Fox inline TEXT function to create new XAML content


            TEXT TO xaml textmerge noshow


        <Canvas Name=”MyPanel”


            xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”


            xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”


            Background=”AliceBlue”


            >


                  <Canvas.Resources>


                        <Style


                      TargetType=”ListBoxItem”>


                      <Setter Property=”Foreground” Value=”Blue”/>


                      <Style.Triggers>


                          <Trigger Property=”IsSelected” Value=”True”>


                              <Setter Property=”Foreground” Value=”White”/>


                              <Setter Property=”Background” Value=”Aquamarine”/>


                          </Trigger>


                          <Trigger Property=”IsMouseOver” Value=”True”>


                              <Setter Property=”Foreground” Value=”Red”/>


                          </Trigger>


                      </Style.Triggers>


                        </Style>


                  </Canvas.Resources>


                  <TextBlock Canvas.Top=”5″ Canvas.Left=”300″ Height=”20″ Foreground=”Red” Background=”LightGreen”>


                        <TextBlock.Text>


                              <Binding ElementName=”MyListBox” Path=”SelectedItem.Content.Text”/>


                        </TextBlock.Text>


                  </TextBlock>


            <Grid Canvas.Top=”25″ >


                  <Grid.ColumnDefinitions>


                        <ColumnDefinition/>


                        <ColumnDefinition/>


                  </Grid.ColumnDefinitions>


                  <Grid.RowDefinitions>


                        <RowDefinition/>


                  </Grid.RowDefinitions>


            <ListBox Name=”MyListBox” Height=”300″ Grid.Column=”0″>


        ENDTEXT


        SET TEXTMERGE ON TO memvar xaml ADDITIVE


        cPath=”e:\pictures\2007\12\02\”   && Some path with pictures/movies


        nFiles=ADIR(aaFiles,cPath+”*.*”) 


        FOR i = 1 TO nFiles


            IF LOWER(JUSTEXT(aaFiles[i,1]))$”jpg avi”


                  \<ListBoxItem Width=”240″><TextBlock>


                  \<<cPath+TRANSFORM(aaFiles(i,1))>>


                  \</TextBlock>


                  \</ListBoxItem>


              ENDIF


        ENDFOR


        SET TEXTMERGE to


            TEXT TO xaml textmerge NOSHOW ADDITIVE


                  </ListBox>


                  <MediaElement Grid.Column=”1″ Height=”300″>


                        <MediaElement.Source>


                              <Binding ElementName=”MyListBox” Path=”SelectedItem.Content.Text”/>


                        </MediaElement.Source>


                  </MediaElement>


                  </Grid>


        </Canvas>


            ENDTEXT


            ?xaml


            thisform.ocWPF.SetXAML(xaml)


ENDDEFINE


 


</FoxClient Code>


 


 


 


 


See also:


 


Create a .Net UserControl that calls a web service that acts as an ActiveX control to use in Excel, VB6, Foxpro


 


 


<Sample Code>


Imports System.Windows.Forms.Integration


Imports System.Windows.Controls


Imports System.Windows.Media


Imports System.Windows.Input


Imports Microsoft.Win32


Imports System.Windows.Forms


Imports System.Runtime.InteropServices


 


‘ the Guids are gen’d from UUIDGEN.


<Microsoft.VisualBasic.ComClass(“2c8298fa-f4d2-4c18-9a99-280d83ac3f03”, “397d45fd-4621-4278-ab5a-8d28f9120dc1”)> _


Public Class WPFControl


    Sub New()


 


        ‘ This call is required by the Windows Form Designer.


        InitializeComponent()


 


        ‘ Add any initialization after the InitializeComponent() call.


        Me.Width = 800


        Me.Height = 600


        Me.BackColor = System.Drawing.Color.AliceBlue


 


        Dim elemHost As New ElementHost


        Me.Controls.Add(elemHost)


        elemHost.Top = 30   ‘ move down a little bit


        ‘see http://blogs.msdn.com/calvin_hsia/archive/2007/11/29/6600915.aspx


        Dim xaml = _


        <Canvas Name=MyPanel


            xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation


            xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml


            Background=LightGreen


            >


            <TextBlock Name=MyTextBlock Canvas.Top=10>WPF Embedded Hi there


                <TextBlock.Triggers>


                    <EventTrigger RoutedEvent=TextBlock.Loaded>


                        <BeginStoryboard>


                            <Storyboard>


                                <DoubleAnimation


                                    Storyboard.TargetName=MyTextBlock


                                    Storyboard.TargetProperty=(Opacity)


                                    From=1.0 To=.10 Duration=0:0:1 AutoReverse=True RepeatBehavior=Forever/>


                            </Storyboard>


                        </BeginStoryboard>


                    </EventTrigger>


                </TextBlock.Triggers>


            </TextBlock>


            <Ellipse Height=150 HorizontalAlignment=Left Canvas.Top=30>


                <Ellipse.Fill>


                    <SolidColorBrush x:Name=ebrush Color=Black/>


                </Ellipse.Fill>


                <Ellipse.Triggers>


                    <EventTrigger RoutedEvent=Ellipse.Loaded>


                        <BeginStoryboard>


                            <Storyboard


                                TargetProperty=(Ellipse.Width)>


                                <DoubleAnimation From=20 To=200 Duration=0:0:5 AutoReverse=True RepeatBehavior=Forever/>


                                <ColorAnimation


                                    Storyboard.TargetName=ebrush


                                    Storyboard.TargetProperty=Color


                                    From=Red To=Blue Duration=0:0:3 AutoReverse=True RepeatBehavior=Forever/>


                            </Storyboard>


                        </BeginStoryboard>


                    </EventTrigger>


                </Ellipse.Triggers>


            </Ellipse>


        </Canvas>


        elemHost.Child = CType(System.Windows.Markup.XamlReader.Load(xaml.CreateReader), System.Windows.UIElement)


 


        Dim MyCanvas = CType(elemHost.Child, Canvas)


        elemHost.Height = 400


        elemHost.Width = 600


        Dim btn As New Windows.Forms.Button ‘ This is a WinForms button: not a wpf System.Windows.Controls.Button


        btn.Visible = True


        btn.Text = “Winform Btn to launch WPF form”


        btn.AutoSize = True


        btn.BackColor = System.Drawing.Color.Bisque


        Dim WinFormHost = New WindowsFormsHost


        WinFormHost.Child = btn


        AddHandler btn.Click, AddressOf btn_Click


        Canvas.SetTop(WinFormHost, 80)


        MyCanvas.Children.Add(WinFormHost)


 


        MyCanvas.RenderTransform = New ScaleTransform(1, 1)


        AddHandler MyCanvas.MouseWheel, AddressOf MyCanvas_MouseWheel


 


 


    End Sub


    Public Sub SetXAML(ByVal xaml As String)


        Try


            Dim tr As New IO.StringReader(xaml)


            Dim xml = XElement.Load(tr)


            Dim x = System.Windows.Markup.XamlReader.Load(xml.CreateReader)


            Dim elemHost = CType(Me.Controls(0), ElementHost)


            elemHost.Child = x


        Catch ex As Exception


            MessageBox.Show(ex.Message)


        End Try


 


    End Sub


    Sub btn_Click()


        Dim oWPFForm = New WPFForm


        oWPFForm.ShowDialog()


    End Sub


    Sub MyCanvas_MouseWheel(ByVal o As Canvas, ByVal e As MouseWheelEventArgs)


        Dim tr = CType(o.RenderTransform, ScaleTransform)


        If e.Delta > 0 Then


            tr.ScaleX *= 1.1


            tr.ScaleY *= 1.1


        Else


            tr.ScaleX /= 1.1


            tr.ScaleY /= 1.1


        End If


    End Sub


    <ComRegisterFunction()> _


        Public Shared Sub Register(ByVal t As Type)


        ComRegistration.RegisterControl(t)


    End Sub


 


    <ComUnregisterFunction()> _


    Public Shared Sub Unregister(ByVal t As Type)


        ComRegistration.UnregisterControl(t)


    End Sub


 


    Private Sub WPFControl_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseDown


        MsgBox(“AA”)


    End Sub


End Class


 


Class WPFForm : Inherits Windows.Window


    Sub New()


        Dim xaml = _


        <StackPanel


            xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation


            xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml


            Orientation=Vertical


            >


            <TextBlock>WPFForm Hi there</TextBlock>


        </StackPanel>


        Me.Content = System.Windows.Markup.XamlReader.Load(xaml.CreateReader)


    End Sub


 


 


End Class


 


Friend Module ComRegistration


    Public Sub RegisterControl(ByVal t As Type)


        Try


            GuardNullType(t, “t”)


            GuardTypeIsControl(t)


            ‘ CLSID


            Dim key As String = “CLSID\” & t.GUID.ToString(“B”)


            Using subkey As RegistryKey = Registry.ClassesRoot.OpenSubKey(key, True)


                ‘ Control


                Using controlKey As RegistryKey = subkey.CreateSubKey(“Control”)


                End Using


                ‘ Misc


                Using miscKey As RegistryKey = subkey.CreateSubKey(“MiscStatus”)


                    miscKey.SetValue(“”, “131457”, RegistryValueKind.String)


                End Using


                ‘ TypeLib


                Using typeLibKey As RegistryKey = subkey.CreateSubKey(“TypeLib”)


                    Dim libId As Guid = System.Runtime.InteropServices.Marshal.GetTypeLibGuidForAssembly(t.Assembly)


                    typeLibKey.SetValue(“”, libId.ToString(“B”), RegistryValueKind.String)


                End Using


                ‘ Version


                Using versionKey As RegistryKey = subkey.CreateSubKey(“Version”)


                    Dim major, minor As Integer


                    System.Runtime.InteropServices.Marshal.GetTypeLibVersionForAssembly(t.Assembly, major, minor)


                    versionKey.SetValue(“”, String.Format(“{0}.{1}”, major, minor))


                End Using


            End Using


        Catch ex As Exception


            HandleException(“ComRegisterFunction failed.”, t, ex)


        End Try


    End Sub


 


    Public Sub UnregisterControl(ByVal t As Type)


        Try


            GuardNullType(t, “t”)


            GuardTypeIsControl(t)


            ‘ CLSID


            Dim key As String = “CLSID\” & t.GUID.ToString(“B”)


            Registry.ClassesRoot.DeleteSubKeyTree(key)


        Catch ex As Exception


            HandleException(“ComUnregisterFunction failed.”, t, ex)


        End Try


    End Sub


    Private Sub GuardNullType(ByVal t As Type, ByVal param As String)


        If t Is Nothing Then


            Throw New ArgumentException(“The CLR type must be specified.”, param)


        End If


    End Sub


    Private Sub GuardTypeIsControl(ByVal t As Type)


        If Not GetType(System.Windows.Forms.Control).IsAssignableFrom(t) Then


            Throw New ArgumentException(“Type argument must be a Windows Forms control.”)


        End If


    End Sub


 


    Private Sub HandleException(ByVal message As String, ByVal t As Type, ByVal ex As Exception)


        Try


            If t IsNot Nothing Then


                message &= vbCrLf & String.Format(“CLR class ‘{0}'”, t.FullName)


            End If


            Throw New Exception(message, ex) ‘ replace with custom exception type


        Catch ex2 As Exception


            My.Application.Log.WriteException(ex2)


        End Try


    End Sub


End Module


 


 


 


</Sample Code>