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. ;)