Udostępnij za pośrednictwem


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="https://schemas.microsoft.com/winfx/2006/xaml/presentation"

            xmlns:x="https://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="https://schemas.microsoft.com/winfx/2006/xaml/presentation"

                    xmlns:x="https://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="https://schemas.microsoft.com/winfx/2006/xaml/presentation"

                            xmlns:x="https://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="https://schemas.microsoft.com/winfx/2006/xaml/presentation"

                                    xmlns:x="https://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="https://schemas.microsoft.com/winfx/2006/xaml/presentation"

                xmlns:x="https://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