How to Create dynamic XAML to display arbitrary XML


Here’s a sample of creating dynamic XAML to display arbitrary XML


 


You can use the XML DataProvider to supply XML data to XAML, a web service, or you can use a Query.  Each way, you can generate XAML dynamically to display the XML. (echoes of XSLT)


 


I took the prior sample (Create your own media browser: Display your pictures, music, movies in a XAML tooltip) and changed the query to generate XML:


 


            Dim Query = _


                From file In IO.Directory.GetFiles(_rootFolder, _


                    “*.*”, IO.SearchOption.AllDirectories) _


                Select file, Ext = IO.Path.GetExtension(file).ToLower() _


                Where Ext.Length > 0 AndAlso “.avi .jpg .mid .mpg .wma .wmv”.Contains(Ext) _


                Select <File>


                           <FileName><%= file.Substring(_rootFolder.Length + 1) %></FileName>


                           <Type><%= Ext.Substring(1) %></Type>


                           <Size><%= New IO.FileInfo(file).Length %></Size>


                       </File>


 


The query result is an IEnumerable(Of System.Xml.Linq.XElement), which is passed to the Browse class, which dynamically finds the XML elements and binds the XAML


 


The Browse code detects if the query is an Anonymous Type or XML. Either way, it dynamically creates the columns for displaying the results of a query that creates XML.


 


 


Code using the XMLProvider to supply the XML results of a query are shown below too (Query3). Note that the XAML for the ListBox is hardcoded to the XML Elements, such as Filename and Size. It the query were to change, then the XAML will have to change too.


 


See also


Use a simple XSLT to read the RSS feed from a blog


Start using XML and XSLT to create HTML


Do you like reading a blog author? Retrieve all blog entries locally for reading/searching using XML, XSLT, XPATH


Use new XML Features of VB to generate dynamic scripts and text files


 


<Code Sample>


Imports System.Windows.Controls.Primitives  ‘ for Popup


 


Partial Public Class Window1


    Dim _rootFolder As String = System.Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)


    Private Sub Window1_Loaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded


        Me.Width = 800 : Me.Height = 800


        If 0 Then


            ‘ this is the original way: generate a query that can have arbitrary result columns and just show them


            Dim Query2 = From file In IO.Directory.GetFiles(_rootFolder, _


                                            “*.*”, IO.SearchOption.AllDirectories) _


                                            Select file, Ext = IO.Path.GetExtension(file).ToLower _


                                            Where Ext.Length > 0 AndAlso “.avi .jpg .mid .mpg .wma .wmv”.Contains(Ext) _


                                     Select FileName = file.Substring(_rootFolder.Length + 1), _


                                         Type = Ext.Substring(1), _


                                         Size = (New IO.FileInfo(file)).Length


            Me.Content = New BrowseWithMedia(Query2, Me)


            Return


        Else


            ‘ this is creating an XML fragment of the same data


            Dim Query = _


                From file In IO.Directory.GetFiles(_rootFolder, _


                    “*.*”, IO.SearchOption.AllDirectories) _


                Select file, Ext = IO.Path.GetExtension(file).ToLower() _


                Where Ext.Length > 0 AndAlso “.avi .jpg .mid .mpg .wma .wmv”.Contains(Ext) _


                Select <File>


                           <FileName><%= file.Substring(_rootFolder.Length + 1) %></FileName>


                           <Type><%= Ext.Substring(1) %></Type>


                           <Size><%= New IO.FileInfo(file).Length %></Size>


                       </File>


            Me.Content = New BrowseWithMedia(Query, Me) ‘ same call with XML and non-XML!


 


            Return


        End If


        ‘ the rest of this method is a way to display XML data using XmlDataProvider, but it’s


        ‘ not a generic way to display XML


        Dim Query3 = From file In IO.Directory.GetFiles(_rootFolder, _


                                        “*.*”, IO.SearchOption.AllDirectories) _


                                        Select file, Ext = IO.Path.GetExtension(file).ToLower _


                                        Where Ext.Length > 0 AndAlso “.avi .mid .mpg .wma .wmv”.Contains(Ext) _


                                 Select FileName = file.Substring(_rootFolder.Length + 1), _


                                     Type = Ext.Substring(1), _


                                     Size = (New IO.FileInfo(file)).Length


        Dim xmlContent = _


        <StackPanel


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


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


            Background=Cornsilk>


 


            <StackPanel.Resources>


                <XmlDataProvider x:Key=FileData XPath=SomeData>


                    <x:XData>


                        <SomeData xmlns=“”>


                            <%= Query3 %>


                        </SomeData>


                    </x:XData>


                </XmlDataProvider>


            </StackPanel.Resources>


 


            <ListBox


                Background=Honeydew>


                <ListBox.ItemsSource>


                    <Binding Source={StaticResource FileData}


                        XPath=*/>


                </ListBox.ItemsSource>


                <ListBox.ItemTemplate>


                    <DataTemplate>


                        <StackPanel Orientation=Horizontal>


                            <TextBlock FontSize=12 Foreground=Black Text={Binding XPath=FileName} Width=300/>


                            <TextBlock FontSize=12 Foreground=Blue Text={Binding XPath=Size}/>


                        </StackPanel>


                    </DataTemplate>


                </ListBox.ItemTemplate>


 


            </ListBox>


        </StackPanel>


        Console.WriteLine(xmlContent.ToString)


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


    End Sub


    Private Sub Window1_Deactivated(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Deactivated


        Dim pop = CType(Me.Content, BrowseWithMedia)._lvPopUp ‘ no popup if user changes active window (Alt-Tab)


        If Not pop Is Nothing AndAlso pop.IsOpen Then


            pop.IsOpen = False


        End If


    End Sub


 


    Private Class BrowseWithMedia : Inherits Browse


        Dim _Parent As Window1      ‘ reference to container


        Friend _lvPopUp As Popup    ‘ popup window to show tip with movie


        Dim WithEvents _lvTimer As New System.Windows.Threading.DispatcherTimer ‘ timer: after delay, show tip


        Sub New(ByVal Query As Object, Optional ByVal Parent As Object = Nothing)


            MyBase.new(Query, Parent)


            _Parent = Parent


        End Sub


        Sub HandleWheel(ByVal sp As StackPanel, ByVal e As System.Windows.Input.MouseWheelEventArgs) ‘Handles Me.MouseWheel


            Dim transform = CType(sp.RenderTransform, ScaleTransform)


            Dim melem As MediaElement = sp.Children(1)


            If e.Delta > 0 Then


                melem.Height *= 1.1


                melem.Width *= 1.1


                ‘transform.ScaleX *= 1.1    ‘ to scale tip text too


                ‘transform.ScaleY *= 1.1


            Else


                ‘transform.ScaleX /= 1.1


                ‘transform.ScaleY /= 1.1


                melem.Height /= 1.1


                melem.Width /= 1.1


 


            End If


        End Sub


        Dim _mdownPt As Point


        Sub HandleClick(ByVal sp As StackPanel, ByVal e As MouseButtonEventArgs)


            If sp.IsMouseCaptured Then


                sp.ReleaseMouseCapture()


            End If


            If e.LeftButton = MouseButtonState.Pressed Then


                _mdownPt = Mouse.GetPosition(sp)    ‘ record mouse pos relative to sp


                sp.CaptureMouse()


            End If


        End Sub


        Sub HandleMouseMove(ByVal sp As StackPanel, ByVal e As MouseEventArgs)


            If Mouse.LeftButton = MouseButtonState.Pressed Then


                If sp.CaptureMouse Then


                    Dim curpt As Point = sp.PointToScreen(Mouse.GetPosition(sp))


                    _lvPopUp.HorizontalOffset = curpt.X – _mdownPt.X    ‘ subtract mouse pos rel to sp


                    _lvPopUp.VerticalOffset = curpt.Y – _mdownPt.Y


                    _lvPopUp.Placement = PlacementMode.Absolute ‘ PlacementMode.Mouse


                End If


            End If


        End Sub


 


        Sub HandlePopupTimerTick() Handles _lvTimer.Tick    ‘ show the popup


            _lvTimer.IsEnabled = False  ‘ enabled in HandleRowSelected


            Dim lbi As ListBoxItem = Me.ItemContainerGenerator.ContainerFromIndex(Me.SelectedIndex)


            If Not lbi Is Nothing Then


                Dim sp As New StackPanel


                Dim cSrcFile = _Parent._rootFolder + IO.Path.DirectorySeparatorChar


                Dim cSize As String


                If lbi.Content.GetType Is GetType(XElement) Then


                    cSrcFile += CType(lbi.Content, XElement)…<FileName>.Value


                    cSize = CType(CType(lbi.Content, XElement)…<Size>.Value / 1024, Int32).ToString(“n0”) + “K”


                Else


                    cSrcFile += lbi.Content.FileName.ToString


                    cSize = CType(lbi.Content.size / 1024, Int32).ToString(“n0”) + “K”


                End If


                Dim XAMLPopup = _


                <Popup


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


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


                    Placement=Right


                    IsOpen=False


                    >


                    <StackPanel Orientation=Vertical>


                        <StackPanel.RenderTransform>


                            <ScaleTransform ScaleX=1 ScaleY=1/>


                        </StackPanel.RenderTransform>


                        <TextBlock Foreground=Black Background=LightYellow>


                            <%= cSrcFile + ” “ + cSize %>


                        </TextBlock>


                        <MediaElement


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


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


                            Name=MyVid Height=250>


                            <MediaElement.Triggers>


                                <EventTrigger RoutedEvent=MediaElement.Loaded>


                                    <EventTrigger.Actions>


                                        <BeginStoryboard>


                                            <Storyboard>


                                                <MediaTimeline Source=<%= cSrcFile %>


                                                    Storyboard.TargetName=MyVid


                                                    RepeatBehavior=Forever/>


                                            </Storyboard>


                                        </BeginStoryboard>


                                    </EventTrigger.Actions>


                                </EventTrigger>


                            </MediaElement.Triggers>


                        </MediaElement>


                    </StackPanel>


                </Popup>


                _lvPopUp = System.Windows.Markup.XamlReader.Load(XAMLPopup.CreateReader)


                _lvPopUp.PlacementTarget = lbi


                _lvPopUp.IsOpen = True


                Dim stackpanel As StackPanel = _lvPopUp.Child ‘ only child is the stackpanel


                AddHandler stackpanel.MouseWheel, AddressOf HandleWheel ‘ mouse wheel will zoom in/out


                AddHandler stackpanel.MouseDown, AddressOf HandleClick  ‘ mouse click/drag will move picture/movie


                AddHandler stackpanel.MouseUp, AddressOf HandleClick


                AddHandler stackpanel.MouseMove, AddressOf HandleMouseMove


 


            End If


        End Sub


        Sub HandleRowSelected(ByVal sender As Object, ByVal e As RoutedEventArgs) Handles Me.SelectionChanged


            Dim lb = CType(sender, ListBox)


            If Not lb Is Nothing AndAlso (lb.SelectedIndex >= 0) Then


                Dim lbi As ListBoxItem = lb.ItemContainerGenerator.ContainerFromIndex(lb.SelectedIndex)


                If Not _lvPopUp Is Nothing AndAlso _lvPopUp.IsOpen Then


                    _lvPopUp.IsOpen = False


                End If


                _lvTimer.Stop()  ‘ stop prior timer, if any


                _lvTimer.Interval = TimeSpan.FromMilliseconds(500)


                _lvTimer.Start()


            End If


        End Sub


    End Class


End Class


 


Class Browse


    Inherits ListView


    Sub New(ByVal Query As Object, Optional ByVal Parent As Object = Nothing)


        Dim gv As New GridView


        Me.View = gv


        Me.ItemsSource = Query


        If Not Parent Is Nothing Then


            If Parent.GetType.BaseType Is GetType(Window) Then


                CType(Parent, Window).Title = “# items = “ + Me.Items.Count.ToString


            End If


        End If


        Me.AddHandler(GridViewColumnHeader.ClickEvent, New RoutedEventHandler(AddressOf HandleHeaderClick))


        If Query.GetType.GetInterface(GetType(IEnumerable(Of )).FullName).GetGenericArguments(0).Name = “XElement” Then ‘ It’s XML


            Dim Elem1 = CType(Query, IEnumerable(Of XElement))(0).Elements  ‘ Thanks Avner!


            For Each Item In Elem1


                Dim gvc As New GridViewColumn


                gvc.Header = Item.Name.LocalName


                gv.Columns.Add(gvc)


                Dim bind As New Binding(“Element[“ + Item.Name.LocalName + “].Value”)


                gvc.DisplayMemberBinding = bind


                gvc.Width = 180


            Next


        Else ‘ it’s some anonymous type like “VB$AnonymousType_1`3”. Let’s use reflection to get the column names


            For Each mem In From mbr In _


                            Query.GetType().GetInterface(GetType(IEnumerable(Of )).FullName) _


                                .GetGenericArguments()(0).GetMembers _


                            Where mbr.MemberType = Reflection.MemberTypes.Property


                Dim datatype = CType(mem, Reflection.PropertyInfo)


                Dim coltype = datatype.PropertyType.Name


                Select Case coltype


                    Case “Int32”, “String”, “Int64”


                        Dim gvc As New GridViewColumn


                        gvc.Header = mem.Name


                        gv.Columns.Add(gvc)


                        If coltype <> “String” Then


                            gvc.Width = 80


                            Dim XAMLdt = _


                                <DataTemplate


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


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


                                    >


                                    <StackPanel Orientation=Horizontal>


                                        <TextBlock Name=tb


                                            Text=<%= “{Binding Path=” + mem.Name + “}” %>


                                            Foreground=Black


                                            FontWeight=Bold


                                            Background=SpringGreen>


                                        </TextBlock>


                                    </StackPanel>


                                </DataTemplate>


                            gvc.CellTemplate = System.Windows.Markup.XamlReader.Load(XAMLdt.CreateReader)


                        Else


                            gvc.DisplayMemberBinding = New Binding(mem.Name)


                            gvc.Width = 180


                        End If


                End Select


            Next


        End If


        Dim XAMLlbStyle = _


            <Style


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


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


                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>


        Me.ItemContainerStyle = Windows.Markup.XamlReader.Load(XAMLlbStyle.CreateReader)


    End Sub


    Dim _Lastdir As System.ComponentModel.ListSortDirection = ComponentModel.ListSortDirection.Ascending


    Dim _LastHeaderClicked As GridViewColumnHeader = Nothing


    Sub HandleHeaderClick(ByVal sender As Object, ByVal e As RoutedEventArgs)


        If e.OriginalSource.GetType Is GetType(GridViewColumnHeader) Then


            Dim gvh = CType(e.OriginalSource, GridViewColumnHeader)


            Dim dir As System.ComponentModel.ListSortDirection = ComponentModel.ListSortDirection.Ascending


            If Not gvh Is Nothing AndAlso Not gvh.Column Is Nothing Then


                Dim hdr = gvh.Column.Header


                If gvh Is _LastHeaderClicked Then


                    If _Lastdir = ComponentModel.ListSortDirection.Ascending Then


                        dir = ComponentModel.ListSortDirection.Descending


                    End If


                End If


                Sort(hdr, dir)


                _LastHeaderClicked = gvh


                _Lastdir = dir


            End If


        End If


    End Sub


    Sub Sort(ByVal sortby As String, ByVal dir As System.ComponentModel.ListSortDirection)


        Me.Items.SortDescriptions.Clear()


        Dim sd = New System.ComponentModel.SortDescription(sortby, dir)


        Me.Items.SortDescriptions.Add(sd)


        Me.Items.Refresh()


    End Sub


End Class


 


</Code Sample>


 

Comments (5)

  1. Ted says:

    While cool, this example leaves me wondering how long it will live since it’s based on technologies with a very short lifespan (less than 3 years).  

    We are facing similar issues with finding a better option than plain JavaScript since replacements are mildly more productive to use, have the downside of a significantly higher maintenance cost and likely a very short lifespan (less than 3 years) due to the flux of web technologies (os, browser, www standard, etc).