composite user controls

Please post public support tickets here. Note: for private support tickets, please send an email to support@cshtml5.com instead.
DRR
Posts: 4
Joined: Tue Dec 01, 2015 1:08 pm

composite user controls

Postby DRR » Tue Dec 01, 2015 3:30 pm

Using beta 6 of C#/XAML for HTML5 (and also in earlier versions), I can successfully create custom controls that emit arbitrary HTML content by deriving a class from Windows.UI.Xaml.Controls.UserControl and overriding the CreateDomElement method (using JSIL.Builtins.Global["document"] to get the ambient document object and calling createElement(), appendChild(), etc.). These controls work as expected when referenced from XAML:

Code: Select all

<myNamespace:MyCompositeUserControl />


It looks like I should be able to create a composite control by setting the domElementWhereToPlaceChildren output parameter properly and then nesting XAML controls. However, as soon as I add a child control in XAML, I get the following compilation error:

Code: Select all

<myNamespace:MyCompositeUserControl>
    <myNamespace:MyChildUserControl />
</myNamespace:MyCompositeUserControl>


1>C:\MyPath\MyPage.xaml : error : C#/XAML for HTML5: XamlPreprocessor (pass 1) failed: Type not found: "MyCompositeUserControl" in namespace: "myNamespace".


It looks like a bug at first glance, but maybe I'm not setting things up correctly. Do you know if composite UserControls are supported, and if so, what is the proper procedure for creating them?

JS-Support @Userware
Site Admin
Posts: 1142
Joined: Tue Apr 08, 2014 3:42 pm

Re: composite user controls

Postby JS-Support @Userware » Wed Dec 02, 2015 9:15 am

Hi and welcome to the forums.

Overriding the "CreateDomElement" method is an internal technique that is currently not intended to be used to create custom controls.

The currently supported options to extend CSHTML5 are listed below:

1. To create custom controls using JavaScript and HTML5: please use the classes and methods defined in "CSharpXamlForHtml5.DomManagement".

Here is an example of "VideoPlayerControl" that is based on the HTML5 "<video>" tag:

Code: Select all

using System;
using System.Windows;
using Windows.UI.Xaml;

namespace SampleNamespace
{
    public class VideoPlayerControl : FrameworkElement
    {
        public VideoPlayerControl()
        {
            // Specify the HTML representation of the control:
            CSharpXamlForHtml5.DomManagement.SetHtmlRepresentation(this, @"<video controls autoplay/>");

            Loaded += VideoPlayerControl_Loaded;
            Unloaded += VideoPlayerControl_Unloaded;
        }

        void VideoPlayerControl_Unloaded(object sender, RoutedEventArgs e)
        {
            var control = (VideoPlayerControl)sender;

            CSharpXamlForHtml5.DomManagement.GetDomElementFromControl(control).removeEventListener("error", (Action<object>)OnMediaFailed);
        }

        void VideoPlayerControl_Loaded(object sender, RoutedEventArgs e)
        {
            var control = (VideoPlayerControl)sender;

            CSharpXamlForHtml5.DomManagement.GetDomElementFromControl(control).addEventListener("error", (Action<object>)OnMediaFailed);
        }


        /// <summary>
        /// Gets or sets a value that indicates whether media will begin playback automatically
        /// when the Source property is set.
        /// </summary>
        public bool AutoPlay
        {
            get { return (bool)GetValue(AutoPlayProperty); }
            set { SetValue(AutoPlayProperty, value); }
        }
        /// <summary>
        /// DependencyProperty for VideoPlayerControl AutoPlay property.
        /// </summary>
        public static readonly DependencyProperty AutoPlayProperty =
            DependencyProperty.Register("AutoPlay", typeof(bool), typeof(VideoPlayerControl), new PropertyMetadata(true, AutoPlay_Changed));

        private static void AutoPlay_Changed(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            var control = (VideoPlayerControl)sender;
            var newValue = (bool)e.NewValue;

            if (CSharpXamlForHtml5.DomManagement.IsControlInVisualTree(control))
            {
                if (newValue)
                {
                    CSharpXamlForHtml5.DomManagement.GetDomElementFromControl(control).autoplay = "true"; //any value should work actually, the very existence of the "controls" attribute makes it display the built-in controls.
                }
                else
                {
                    CSharpXamlForHtml5.DomManagement.GetDomElementFromControl(control).removeAttribute("autoplay");
                }
            }
        }

        /// <summary>
        /// Gets/Sets the Source on this VideoPlayerControl.
        ///
        /// The Source property is the Uri of the media to be played.
        /// </summary>
        public Uri Source
        {
            get { return (Uri)GetValue(SourceProperty); }
            set { SetValue(SourceProperty, value); }
        }

        /// <summary>
        /// DependencyProperty for VideoPlayerControl Source property.
        /// </summary>
        public static readonly DependencyProperty SourceProperty =
            DependencyProperty.Register("Source", typeof(Uri), typeof(VideoPlayerControl), new PropertyMetadata(null, Source_Changed));

        /// <summary>
        /// Raised when source is changed
        /// </summary>
        static void Source_Changed(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            var control = (VideoPlayerControl)sender;
            var newValue = (Uri)e.NewValue;

            // Always check that the control is in the Visual Tree before modifying its HTML representation
            if (CSharpXamlForHtml5.DomManagement.IsControlInVisualTree(control))
            {
                // Verify that the URI is supported:
                string uriLowerCase = newValue.ToString().ToLower();
                if (!uriLowerCase.StartsWith(@"http:/") && !uriLowerCase.StartsWith(@"https:/"))
                {
                    MessageBox.Show("ERROR: In this version, video file location must start with http or https. Please change the URI of the video to play.");
                    return;
                }
                if (!uriLowerCase.EndsWith(@".mp4"))
                {
                    MessageBox.Show("ERROR: To support the biggest number of browsers, only .MP4 video files can be used. Please change the video format to MP4.");
                    return;
                }

                // Update the "src" property of the <video> tag
                CSharpXamlForHtml5.DomManagement.GetDomElementFromControl(control).src = newValue.ToString();
            }
        }


        /// <summary>
        /// Gets or sets a value that indicates whether the player shows the built-in controls (Play, Progression, etc.) or not.
        /// </summary>
        public bool ShowControls
        {
            get { return (bool)GetValue(ShowControlsProperty); }
            set { SetValue(ShowControlsProperty, value); }
        }

        /// <summary>
        /// DependencyProperty for VideoPlayerControl ShowControls property.
        /// </summary>
        public static readonly DependencyProperty ShowControlsProperty =
            DependencyProperty.Register("ShowControls", typeof(bool), typeof(VideoPlayerControl), new PropertyMetadata(true, ShowControls_Changed));

        private static void ShowControls_Changed(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            var control = (VideoPlayerControl)sender;
            var newValue = (bool)e.NewValue;

            if (CSharpXamlForHtml5.DomManagement.IsControlInVisualTree(control))
            {
                if (newValue)
                {
                    CSharpXamlForHtml5.DomManagement.GetDomElementFromControl(control).controls = "true"; //any value should work actually, the very existence of the "controls" attribute makes it display the built-in controls.
                }
                else
                {
                    CSharpXamlForHtml5.DomManagement.GetDomElementFromControl(control).removeAttribute("controls");
                }
            }
        }

        private void OnMediaFailed(object e)
        {
            MessageBox.Show("Unable to load: " + Source.OriginalString);
        }

        public event ExceptionRoutedEventHandler MediaFailed;

    }
}


You can test the class above with the following code:

Code: Select all

<Page
    x:Class="PreviewOnWinRT.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:SampleNamespace">
   
    <local:VideoPlayerControl Source="http://www.w3schools.com/html/mov_bbb.mp4"/>
   
</Page>
           


Please note that:
- The sample above does not work in the simulator because the simulator does not support MP4 files at the moment
- The API above currently does not work when used under VS 2015 - we are working on VS 2015 support, and we hope to finish it in early 2016. This is why we haven't documented the feature yet.
- The API above may slightly evolve in the coming Beta's during the first half of 2016.
- The API alone does not allow to compose/nest controls or to create panels. You need to combine it with the "UserControl" technique described below to get such a result.


2. To create custom controls using C# and XAML: do the same as you would do in a WPF, UWP, Silverlight, WinRT, or Windows Phone application, that is, create either a UserControl or a "templated control".

The "templated control" technique is not yet supported because the current Beta 6 has no support for "ControlTemplate". We are working on it and expect it to be available in 2016. We are also working on support for the "ContentProperty" attribute.

In the meantime, you can use the UserControl technique. Here is how it works:
a. Create a standard CSHTML5 UserControl. To do so, right-click on your project in the "Solution Explorer" and click "Add" -> "New Item...", and select "C#/XAML for HTML5" -> "UserControl".
b. Put a container in the XAML code. It can be a border, a ContentControl, or any other container.
c. In the code behind, create a new dependency property for the "Body", and in the "Body_Changed" event, put the content into the aforementioned container.

Here is an example of "SampleHeaderedContainer" based on the technique described above:
XAML:

Code: Select all

<UserControl
    x:Class="SampleNamespace.SampleHeaderedContainer"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
   
    <StackPanel>
        <TextBlock x:Name="HeaderContainer1"/>
        <Border x:Name="BodyContainer1"/>
    </StackPanel>
</UserControl>

C#:

Code: Select all

using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace SampleNamespace
{
    public sealed partial class SampleHeaderedContainer : UserControl
    {
        public SampleHeaderedContainer()
        {
            this.InitializeComponent();
        }

        // Important: do not name the property below "Content", otherwise it will conflict with the existing "Content" property of the UserControl. This limitation will be lifted when CSHTML5 provides the "OverrideMetadata" method.
        public UIElement Body
        {
            get { return (UIElement)GetValue(BodyProperty); }
            set { SetValue(BodyProperty, value); }
        }

        public static readonly DependencyProperty BodyProperty =
            DependencyProperty.Register("Body", typeof(UIElement), typeof(SampleHeaderedContainer), new PropertyMetadata(null, Body_Changed));

        static void Body_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var control = (SampleHeaderedContainer)d;
            if (CSharpXamlForHtml5.DomManagement.IsControlInVisualTree(control))
            {
                control.BodyContainer1.Child = e.NewValue as UIElement;
            }
        }

        public string Header
        {
            get { return (string)GetValue(HeaderProperty); }
            set { SetValue(HeaderProperty, value); }
        }

        public static readonly DependencyProperty HeaderProperty =
            DependencyProperty.Register("Header", typeof(string), typeof(SampleHeaderedContainer), new PropertyMetadata("", Header_Changed));

        static void Header_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var control = (SampleHeaderedContainer)d;
            if (CSharpXamlForHtml5.DomManagement.IsControlInVisualTree(control))
            {
                control.HeaderContainer1.Text = (e.NewValue ?? "").ToString();
            }
        }
    }
}


To test the control, please use code such as the following one:

Code: Select all

<Page
    x:Class="PreviewOnWinRT.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:SampleNamespace">
            <local:SampleHeaderedContainer Header="This is the Header">
                <local:SampleHeaderedContainer.Body>
                    <TextBlock Text="This is the Body"/>
                </local:SampleHeaderedContainer.Body>
            </local:SampleHeaderedContainer>
</Page>



Regards,
JS-Support


Return to “Technical Support”

Who is online

Users browsing this forum: No registered users and 17 guests

 

 

cron