How to find an element in a DataTemplate in WinRT/XAML.

Here’s one way to find a named element in a DataTemplate in XAML in Windows 8 XAML.

You might try FindName to discover it doesn’t work. That’s because it’s not recursive.

So, I created a simple extension method to do the same thing:

public static class FrameworkElementExtensions
{
    public static FrameworkElement FindDescendantByName(this FrameworkElement element, string name)
    {
        if (element == null || string.IsNullOrWhiteSpace(name)) { return null; }

        if (name.Equals(element.Name, StringComparison.OrdinalIgnoreCase))
        {
            return element;
        }
        var childCount = VisualTreeHelper.GetChildrenCount(element);
        for (int i = 0; i < childCount; i++)
        {
            var result = (VisualTreeHelper.GetChild(element, i) as FrameworkElement).FindDescendantByName(name);
            if (result != null) { return result; }
        }
        return null;
    }
}

The code above loops through the children and attempts to match by name.

A simple usage (admittedly, this is stupid and clunky as lists are often virtualized, but …)

private void Page_Loaded_1(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
    for (int i = 0; i < myPeeps.Items.Count; i++)
    {
        var element = myPeeps.ItemContainerGenerator.ContainerFromIndex(i) as FrameworkElement;
        if (element != null)
        {
            var tb = element.FindDescendantByName("tbValue") as TextBlock;
            if (tb != null)
            {
                tb.Text = i.ToString();
            }
        }
    }
}

Given this DataTemplate and Page:

<Page.Resources>
    <local:SampleDataSource x:Key="sampleData" />
    <DataTemplate x:Key="SampleDataTemplate">
        <Grid >
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            <TextBlock HorizontalAlignment="Left" Text="{Binding Name}" />
            <TextBlock Grid.Row="1" Name="tbValue" />
        </Grid>
    </DataTemplate>
</Page.Resources>

<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}" 
      DataContext="{StaticResource sampleData}">
    <ItemsControl x:Name="myPeeps"
        ItemsSource = "{Binding Persons}"
        ItemTemplate="{StaticResource SampleDataTemplate}" FontSize="22"/>
</Grid>

And this sample data:

public class Person {
    public string Name { get; set; }        
}    

public class SampleDataSource {
    public IEnumerable<Person> Persons { get; private set; }
    
    
    public SampleDataSource () {
        var list = new List<Person>();
        
        list.Add(new Person { Name = "Alex" });
        list.Add(new Person { Name = "Betsy" });
        list.Add(new Person { Name = "Carla" });
        list.Add(new Person { Name = "Donald" });
        list.Add(new Person { Name = "Erica" });
        list.Add(new Person { Name = "Francis" });            
        
        this.Persons = list;
    }            
}

You’ll get results like this:

image

One way to find all Metro/Windows 8 Modern UI applications in C#

Running this code as an administrator, you can use the following snippet as a method for determining the nature of a process on Windows 8 and whether it would seem that the running process is running in the new Modern UI shell (metro).

static void Main(string[] args)
{            
    var allProcesses = Process.GetProcesses().Where(p =>
    {
        try
        {
            var pid = p.Id;
            var modules = p.Modules.Cast<ProcessModule>()
                .Where(m => m.FileName.Contains("System.Runtime.WindowsRuntime"));
            return modules.Count() > 0;
        }
        catch { return false; }
    }).OrderBy(p => p.MainModule.FileName).ToList();
    
    for (int i = 0; i < allProcesses.Count(); i++)
    {
        var p = allProcesses[i];
        Console.WriteLine(string.Format("{0}. {1}", i, p.MainModule.FileName));
    }
    Console.ReadKey();
}

Modern UI / Metro applications are protected and cannot be easily interrogated by a non-administrative process. While you can get some basics about all processes, a standard user process isn’t allowed to look at the loaded modules for example.

In the code above, all processes are scanned for a particular DLL. In this case, System.Runtime.WindowsRuntime. I’m not 100% confident this is the best choice … there may be a few better options (or multiple that are required). (If you know of them, please leave a comment!). It did find the Modern UI / Metro applications running in my Windows 8 VM.

Once gathered, the code just outputs the basics to the console. (The name of the host, which is WWAHost.exe apparently some times).

Next step is to learn something useful via the process object.

Forcing IE to run as IE9 with embedded web browser

If you embed IE in a windows application, you may notice that it runs in IE7 Emulated mode. This is likely NOT what you want.

I’ve added code like below to solve the problem:

string appName = ""; try { appName = Path.GetFileName(Assembly.GetEntryAssembly().Location);

const string IE_EMULATION = @"Software\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_BROWSER_EMULATION";

using (var fbeKey = Registry.CurrentUser.OpenSubKey(IE_EMULATION, true))

{ 

fbeKey.SetValue(appName, 9000, RegistryValueKind.DWord);

}
}

catch(Exception ex) { MessageBox.Show(appName + "\n" + ex.ToString(), "Unexpected error setting browser mode!"); }

There’s a special registry key that must be set before IE9 loads and is navigated … it must be set to the name of your application. Oddly, not the full path to your application, just the name of your executable.

You may want to delete this registry setting when your application exits.

WinRT/Xaml/AKA Metro DataTemplate selection based on Data Types

You may have noticed that WinRT does not have automatic resolution of a DataTemplate based on the data type of object added to an ItemsControl. While unfortunate as this behavior is quite handy, it’s not too difficult to replicate the functionality using a DataTemplateSelector.

WPF for example, could do something like this:

<DataTemplate DataType="{x:Type local:Task}">
<StackPanel>
<TextBlock Text="{Binding Path=TaskName}" />
<TextBlock Text="{Binding Path=Description}"/>
<TextBlock Text="{Binding Path=Priority}"/>
</StackPanel>
</DataTemplate>

When the Task type as shown above was found in a list, it would have been rendered as a StackPanel with three TextBlocks automatically. That rocked.

WinRT (Metro/Xaml, Windows 8 applications) are missing the DataType property of DataTemplates. Yes, some of you might say it’s not missing as it’s V1, but given the heritage of Windows 8 Xaml applications, I consider it missing.

While an exact duplicate of the functionality isn’t possible, it’s relatively simple to get close.

<GridView
    x:Name="itemGridView"
    ItemsSource="{Binding Source={StaticResource groupedItemsViewSource}}"
    ItemTemplateSelector="{StaticResource typedTemplateSelector}"
    SelectionMode="None"
    IsItemClickEnabled="True">

Take the GridView above for example (using the template project in Visual Studio 2012).

I’ve assigned the ItemTemplateSelector to an instance of the TypedTemplateSelector class I’ve created.

In the Resources for the Page, I added a custom DataTemplate:

image

I’ve added a DataTemplate with a Key called Type : SampleDataItem (without the spaces though – they’re auto converted by my WordPress theme to a squiggle face: Confused smile).

There’s nothing special about the Template, just the name. It must start with Type:.

Here the custom template selector is being constructed in the Resources:

    <local:TypedTemplateSelector x:Key="typedTemplateSelector"
                                 DefaultTemplateKey="Standard250x250ItemTemplate" />
</Page.Resources>

Here’s the class that you’d need below. It has a caching feature (IsCacheEnabled) to prevent the search from occurring more than once for a key. It’s set to True by default. When used, the object searches from the current Item through all Parents trying to match the type of the object (via the VisualTreeHelper). Only the ClassName is used as programmed below (and not the full namespace). You could easily change this behavior by removing the Split and Last calls.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;

namespace WiredPrairie.TemplateSelector
{
    public class TypedTemplateSelector : DataTemplateSelector
    {
        private Dictionary<string, DataTemplate> _cachedDataTemplates;

        /// <summary>
        /// Fallback value for DataTemplate
        /// </summary>
        public string DefaultTemplateKey { get; set; }

        /// <summary>
        /// Cache search results for a type (defaults to Enabled)
        /// </summary>
        public bool IsCacheEnabled { get; set; }

        public TypedTemplateSelector()
        {
            IsCacheEnabled = true;
        }

        protected override Windows.UI.Xaml.DataTemplate SelectTemplateCore(object item, Windows.UI.Xaml.DependencyObject container)
        {
            // grab the Type name. Type will be searched as Type:NAME as shown below
            /*
                <DataTemplate x:Key="Type:SampleDataItem">
                    <Grid HorizontalAlignment="Left" Width="250" Height="250">
                        <TextBlock Text="{Binding Title}" />
                    </Grid>
                </DataTemplate>
             */
            string key = item != null ? string.Format("Type:{0}", item.GetType().Name.Split('.').Last()) : DefaultTemplateKey;
            DataTemplate dt = GetCachedDataTemplate(key);
            try
            {
                if (dt != null) { return dt; }

                // look at all parents (visual parents)
                FrameworkElement fe = container as FrameworkElement;
                while (fe != null)
                {
                    dt = FindTemplate(fe, key);
                    if (dt != null) { return dt; }
                    // if you were to just look at logical parents,
                    // you'd find that there isn't a Parent for Items set
                    fe = VisualTreeHelper.GetParent(fe) as FrameworkElement;
                }

                dt = FindTemplate(null, key);
                return dt;
            }
            finally
            {
                if (dt != null)
                {
                    AddCachedDataTemplate(key, dt);
                }
            }
        }

        private DataTemplate GetCachedDataTemplate(string key)
        {
            if (!IsCacheEnabled) { return null; }
            VerifyCachedDataTemplateStorage();
            if (_cachedDataTemplates.ContainsKey(key))
            {
                return _cachedDataTemplates[key];
            }

            return null;
        }

        private void AddCachedDataTemplate(string key, DataTemplate dt)
        {
            if (!IsCacheEnabled) { return; }
            VerifyCachedDataTemplateStorage();
            _cachedDataTemplates[key] = dt;
        }

        /// <summary>
        /// Delay creates storage
        /// </summary>
        private void VerifyCachedDataTemplateStorage()
        {
            if (_cachedDataTemplates == null)
            {
                _cachedDataTemplates = new Dictionary<string, DataTemplate>();
            }

        }

        /// <summary>
        /// Returns a template
        /// </summary>
        /// <param name="source">Pass null to search entire app</param>
        /// <param name="key"></param>
        /// <returns></returns>
        private static DataTemplate FindTemplate(object source, string key)
        {
            var fe = source as FrameworkElement;
            object obj;
            ResourceDictionary rd = fe != null ? fe.Resources : App.Current.Resources;
            if (rd.TryGetValue(key, out obj))
            {
                DataTemplate dt = obj as DataTemplate;
                if (dt != null)
                {
                    return dt;
                }
            }
            return null;

        }
    }
}

Windows 8 WinRT/Metro Missing UpdateSourceTrigger

If you’ve done WPF or Silverlight programming, you may have found an occasion where using the Binding property UpdateSourceTrigger set to PropertyChanged was extremely useful. (I know I have!)

It may have looked something like this:

<TextBox Text="{Binding Path=Value, UpdateSourceTrigger=PropertyChanged}" />

The key feature was the live updating of the property through the binding. For example, as the user typed in the TextBox above, the property Value in the bound object would have been updated as the user typed. The default behavior in Silverlight 5 and WPF is to use LostFocus: the Value is only updated when the TextBox loses focus. Sometimes, that’s OK.

In WinRT/Metro however, this option was completely removed, and no decent replacement was provided unfortunately. In searching for a reasonable work-around, I discovered a few other related missing pieces from WinRT/XAML:

  • BindingOptions.GetBinding: This allowed the WPF programmer a method for retrieving a Binding object. I was interested in this option as I wanted to clone the original Binding, make it two-way, and attach to the TextChanged event so that a new binding could be updated when the text changed, with the new binding pointing to the same property as the Text property. (My solution uses the basic idea as this).
  • FrameworkElement.GetBindingExpression: Again, gone. Same basic logic as above. (Probably uses same underlying support that just isn’t there).
  • XamlWriter.Save: XamlWriter is completely gone. I thought maybe I’d save an instance of the TextBox to Xaml, and create a new one with adjusted bindings from the Xaml. XamlReader does exist.

You can start to feel how Xaml really is just a thin wrapper on the Windows 8 run time as you start to look around to see what remains from WPF. Even traditional Silverlight features are gone unfortunately. Maybe Windows 9? Smile

So, I took a round-about approach to solving this problem.

 public class UpdateSourceHelper : FrameworkElement
 {
     public static string GetUpdateSourceText(DependencyObject obj)
     {
         return (string)obj.GetValue(UpdateSourceTextProperty);
     }

     public static void SetUpdateSourceText(DependencyObject obj, string value)
     {
         obj.SetValue(UpdateSourceTextProperty, value);
     }

     // Using a DependencyProperty as the backing store for UpdateSourceText.  This enables animation, styling, binding, etc...
     public static readonly DependencyProperty UpdateSourceTextProperty =
         DependencyProperty.RegisterAttached("UpdateSourceText", typeof(string), typeof(UpdateSourceHelper), new PropertyMetadata(""));
     
     public static bool GetIsEnabled(DependencyObject obj)
     {
         return (bool)obj.GetValue(IsEnabledProperty);
     }

     public static void SetIsEnabled(DependencyObject obj, bool value)
     {
         obj.SetValue(IsEnabledProperty, value);
     }

     // Using a DependencyProperty as the backing store for IsEnabled.  This enables animation, styling, binding, etc...
     public static readonly DependencyProperty IsEnabledProperty =
         DependencyProperty.RegisterAttached("IsEnabled", typeof(bool), typeof(UpdateSourceHelper), 
             new PropertyMetadata(false, 
             // property changed
             (obj, args)=> 
             {                    
                 if (obj is TextBox)
                 {
                     TextBox tb = (TextBox)obj;
                     if ((bool)args.NewValue)
                     {                                                                                      
                         tb.TextChanged += AttachedTextBoxTextChanged;
                     }
                     else
                     {
                         tb.TextChanged -= AttachedTextBoxTextChanged;
                     }
                 }
             }
         ));

     static void AttachedTextBoxTextChanged(object sender, TextChangedEventArgs e)
     {
         if (sender is TextBox)
         {
             var tb = (TextBox)sender;
             tb.SetValue(UpdateSourceHelper.UpdateSourceTextProperty, tb.Text);                
         }
     }        
 }

And here it is in use:

xmlns:local="using:WiredPrairie.Converter"

(The above namespace is needed to provide context to the Xaml parser for the attached properties. Change it to whatever you need).

<TextBox Height="Auto" Margin="0,6" Grid.Row="1" TextWrapping="Wrap" TabIndex="0" 
         Text="{Binding Value}" 
         local:UpdateSourceHelper.IsEnabled="True" 
         local:UpdateSourceHelper.UpdateSourceText="{Binding Value, Mode=TwoWay}"/>

A classic pattern in WPF for extending built in controls without subclassing was to create an attached behavior. Thankfully, this pattern still works.

The core concept is to attach to the original instance of the TextBox by using an enabling property. In this case, an attached property  called IsEnabled is added to the TextBox.

When the value of the IsEnabled property changes from the default of false, the PropertyChanged callback is called. It’s here that the simple magic happens. As long as the attached property is attached to an instance of TextBox, it wires up to the TextChanged event.

In the TextChanged event handler, the value of the attached TextBox (Text property) is set into the second binding’s value. By doing this, it then sets the datasource’s object’s bound property to the new value immediately.

The annoying part about this solution really is the extra binding that is required. As there isn’t a documented/known API for reading an existing binding, this technique requires that the developer specify the binding twice.

local:UpdateSourceHelper.UpdateSourceText="{Binding Value, Mode=TwoWay}"

In WPF, it would have been possible to declare the UpdateSourceText as defaulting to TwoWay binding. But, again, that feature has been removed. Without the TwoWay binding mode, the SetValue call in the TextChanged event handler will not update the object’s bound property. It’s one way.

I looked low and high for a more elegant solution, but couldn’t find one. If you find something better, please leave a comment!