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
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
Anonymous
December 06, 2007
PingBack from http://movies.mediadistricts.com/?p=31823Anonymous
December 10, 2007
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).Anonymous
January 04, 2008
PingBack from http://msdnrss.thecoderblogs.com/2008/01/04/content-rollup-for-november-and-december/Anonymous
January 08, 2008
PingBack from http://aqua.musicnewsandviews.com/2007/12/06/how-to-create-dynamic-xaml-to-display-arbitrary-xml/Anonymous
June 02, 2009
PingBack from http://woodtvstand.info/story.php?id=88802