1

I want get a reference to a MediaElement that is in the first section of a WinRt hub control. One would think this would be trivial but thus far it's been a complete PITA.

I have Googled the problem and found:

How do I access a control inside a XAML DataTemplate?

How to access any control inside Hubsection Datatemplate in Windows 8.1 store

But the code provided does not work.

My (relevant) Xaml is as follows:

 <Hub x:Name="MediaHub">
            <Hub.Header>
                <!-- Back button and page title -->
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="80"/>
                        <ColumnDefinition Width="*"/>
                    </Grid.ColumnDefinitions>
                    <Button  x:Name="backButton" Margin="-1,-1,39,0" Command="{Binding NavigationHelper.GoBackCommand, ElementName=pageRoot}"
                        Style="{StaticResource NavigationBackButtonNormalStyle}"
                        VerticalAlignment="Top"
                        AutomationProperties.Name="Back"
                        AutomationProperties.AutomationId="BackButton"
                        AutomationProperties.ItemType="Navigation Button"/>
                    <TextBlock x:Name="pageTitle" Text="{StaticResource AppName}" Style="{StaticResource HeaderTextBlockStyle}" Grid.Column="1" 
                        IsHitTestVisible="false" TextWrapping="NoWrap" VerticalAlignment="Top"/>
                </Grid>
            </Hub.Header>

            <HubSection Width="780" Margin="0,0,80,0">
                <HubSection.Background>
                    <ImageBrush Stretch="UniformToFill" />
                </HubSection.Background>
                <DataTemplate>
                    <Grid Margin="15">
                        <Grid.RowDefinitions>
                            <RowDefinition Height="500"/>
                            <RowDefinition Height="*"/>
                        </Grid.RowDefinitions>

                        <MediaElement x:Name="videoElement1" AreTransportControlsEnabled="True"/>
                    </Grid>
                </DataTemplate>
            </HubSection>

The findname suggestion as implemented below returns null:

private void navigationHelper_LoadState(object sender, LoadStateEventArgs e)
{
    this.videoElement = this.MediaHub.Sections[0].FindName("videoElement1") as MediaElement;

    if (this.videoElement != null)
    {
        this.videoElement.MediaOpened += VideoElementMediaOpened;
        this.videoElement.MediaFailed += VideoElementMediaFailed;
        this.videoElement.MarkerReached += VideoElementMarkerReached;
    }
}

And the VisualTree suggestion also returns null.

       private void navigationHelper_LoadState(object sender, LoadStateEventArgs e)
        {
            this.videoElement = this.FindChildControl<MediaElement>(this, "videoElement1") as MediaElement;

            if (this.videoElement != null)
            {
                this.videoElement.MediaOpened += VideoElementMediaOpened;
                this.videoElement.MediaFailed += VideoElementMediaFailed;
                this.videoElement.MarkerReached += VideoElementMarkerReached;
            }
        }


  private DependencyObject FindChildControl<T>(DependencyObject control, string ctrlName)
        {
            int childNumber = VisualTreeHelper.GetChildrenCount(control);
            for (int i = 0; i < childNumber; i++)
            {
                var child = VisualTreeHelper.GetChild(control, i);
                var fe = child as FrameworkElement;
                // Not a framework element or is null
                if (fe == null) return null;

                if (child is T && fe.Name == ctrlName)
                {
                    // Found the control so return
                    return child;
                }
                else
                {
                    // Not found it - search children
                    DependencyObject nextLevel = FindChildControl<T>(child, ctrlName);
                    if (nextLevel != null)
                        return nextLevel;
                }
            }
            return null;
        }

In this case the MediaHub is found but when the function FindChildControl is recursively entered using MediaHub as the "control" parameter,

int childNumber = VisualTreeHelper.GetChildrenCount(control);

returns count of 0 and thus it returns and bubbles up a null to the initial call. Though if I set a break point I can see the HubSections count=4 as per the Xaml. (There are more sections defined than what I have entered, omitted for the sake of brevity).

Upon reflection it seems that I'm being forced to using a Xaml based style more like:

<MediaElement x:Name="videoElement1" AreTransportControlsEnabled="True" 
                                  MediaOpened="VideoElement1_OnMediaOpened"
                                  MediaFailed="VideoElement1_OnMediaFailed"
                                  MarkerReached="VideoElement1_OnMarkerReached"
                                  Source="{Binding SomeViewModelProperty}" />

My problem here is that I've often run into issues where using Source= new Uri("xyz") silently fails but using myMediaElement.SetSource(stream, file.ContentType)="xyz" in code for the same source file works 100% of the time. Therefore I want to set this source in code and thus need a reference.

So I don't want this Xaml option explored as an answer to my question but rather how to dynamically access a control in a HubSection of a given Hub. At this point I'd also just like to know for the sake of knowing.

Edit:

As per Dani's answer I have tried installing WinRt toolkit and running the following code but this also returns null.

private void navigationHelper_LoadState(object sender, LoadStateEventArgs e) {
      var m = this.MediaHub.GetDescendantsOfType<MediaElement>().FirstOrDefault();
}

Edit 2:

Ok so basically it all comes down to Page lifecycle's which I haven't yet read much about but running both Danis code and my original code in the Loaded event of the Hub is what is needed. Do that and all example code blocks work:

 public HubPage1()
        {
            this.InitializeComponent();
            this.navigationHelper = new NavigationHelper(this);
            this.navigationHelper.LoadState += navigationHelper_LoadState;
            this.MediaHub.Loaded += MediaHub_Loaded;

        }

        void MediaHub_Loaded(object sender, RoutedEventArgs e)
        {
            var m = this.MediaHub.GetDescendantsOfType<MediaElement>().FirstOrDefault();
            this.videoElement =
                this.FindChildControl<MediaElement>(this.MediaHub, "videoElement1") as
                    MediaElement;
        }

This is like WebForms all over again. ;)

rism
  • 11,932
  • 16
  • 76
  • 116

2 Answers2

3

Here's the way I do it:

<Hub>
    <HubSection>
        <DataTemplate>
            <Button Loaded="MyButton_OnLoad" />
        </DataTemplate>
    </HubSection>    
</Hub>

And then this:

private Button _MyButton = default(Button);
private void MyButton_OnLoad(object sender, RoutedEventArgs e)
{
    _MyButton = sender as Button;
}

Then this:

void MainPage_Loaded(object sender, RoutedEventArgs e)
{
    this._MyButton.Visibility = Visibility.Visible;
}

Is this better than going through the VisualTree? Maybe. It's lighter.

Best of luck!

rism
  • 11,932
  • 16
  • 76
  • 116
Jerry Nixon
  • 31,313
  • 14
  • 117
  • 233
  • I think you meant to type `Button Loaded` in your XAML. It certainly doesn't involve adding the entire bloated toolkit to your app or traversing the visual tree. Other than extracting `ScrollViewers` out of list controls or debugging the UI - I don't think I typically touch the visual tree anywhere else. – Filip Skakun Nov 13 '14 at 21:48
  • Yes that's much better. The penny has begun to drop on a number of things now. Thx. – rism Nov 13 '14 at 22:22
  • Not a reason to unupvote Dani's answer though. It should work as soon as the item loads. – Filip Skakun Nov 13 '14 at 22:24
  • @FilipSkakun I agree. I didn't take away my vote but have shifted to this as the accepted answer. Installing the toolkit has messed up my design time experience. I have squiggles everywhere complaining the "The given key was not present in the dictionary". Code compiles and runs but looks a mess in editor. So anything that simplifies my code and removes a dependency on a 3rd party library is a preferable solution. – rism Nov 13 '14 at 23:43
  • Oh, OK. Sure, I don't disagree. I wouldn't use the toolkit in my app. You shouldn't ship apps with random toolkits. :) – Filip Skakun Nov 14 '14 at 01:46
  • I've heard that toolkit hurts kittens. – Jerry Nixon Dec 12 '14 at 20:16
2

I've tried with the VisualTreeHelperExtensions class from WinRT XAML Toolkit.

There you can call this.MediaHub.GetDescendantsOfType<MediaElement>(); and you'll get videoElement1.

Dani
  • 971
  • 12
  • 31
  • Thx I've installed and tried this and I'm still getting null. Can you advise what event you are using to call that code? Please see my update. I'm using the LoadState event. – rism Nov 13 '14 at 06:48