Page 1 of 1

FileOpenDialog Extension for CSHTML5

Posted: Fri Mar 04, 2016 10:36 am
by JS-Support @Userware
Dear CSHTML5 users,

I am pleased to inform you of the release of the FileOpenDialog Extension for CSHTML5.


It adds the ability to let the user pick a file using the "Open File..." dialog of the browser.

It can also be used for example in conjunction with the ZipFile Extension to load a ZIP file. Note: if you are looking for a way to save files to disk, you can use the FileSaver Extension.


It requires CSHTML5 v1.0 Beta 7.2 or newer.

UPDATE (November 16, 2017): Added support for getting a "DataURI" from a binary file (useful for reading pictures) + Added example of loading a picture (requires v1.0 Beta 12.6 or newer) + Added "FileName" property (thanks to TaterJuice) + Added code for displaying an "OpenFile" ChildWindow.
UPDATE (September 13, 2017): Improved cross-browser compatibility.


To use, simply add a new class named "FileOpenDialog.cs" to your project, and copy/paste the following code:

Code: Select all

using System;
using System.Collections.Generic;
#if SLMIGRATION
using System.Windows;
using System.Windows.Controls;
#else
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
#endif

//------------------------------------
// This is an extension for C#/XAML for HTML5 (www.cshtml5.com)
//
// It requires Beta 7.2 or newer.
//
// It adds the ability to let the user pick a file using the
// "Open File..." dialog of the browser.
//
// It can also be used for example in conjunction with the ZipFile
// Extension to load a ZIP file. Note: if you are looking for
// a way to save files to disk, you can use the FileSaver Extension.
//
// The extension works by adding an <input type='file'> tag to the
// HTML DOM, which will cause the "Open file..." dialog to appear,
// and by listening to its "change" event in order to get a reference
// to the selected file. it returns a JavaScript blob. If the file
// is a plain text file, it will also return a string with the text
// content of the file.
//
// This extension is licensed under the open-source MIT license:
// https://opensource.org/licenses/MIT
//
// Copyright 2017 Userware / CSHTML5
//------------------------------------

namespace CSHTML5.Extensions.FileOpenDialog
{
    public class ControlForDisplayingTheFileOpenDialog : Control
    {
        public event EventHandler<FileOpenedEventArgs> FileOpened;

        private ResultKind _resultKind;
        private string _resultKindStr;
        public ResultKind ResultKind
        {
            get { return _resultKind; }
            set
            {
                _resultKind = value;
                _resultKindStr = value.ToString();
            }
        }

        public ControlForDisplayingTheFileOpenDialog()
        {
            ResultKind = FileOpenDialog.ResultKind.Text; //Note: this is to set the default value of the property.

            CSharpXamlForHtml5.DomManagement.SetHtmlRepresentation(this,
                "<input type='file'>");

            this.Loaded += ControlForDisplayingAFileOpenDialog_Loaded;
        }

        void ControlForDisplayingAFileOpenDialog_Loaded(object sender, RoutedEventArgs e)
        {
            // Get the reference to the "input" element:
            var inputElement = CSHTML5.Interop.GetDiv(this);

            Action<object> onFileOpened = (result) =>
            {
                if (this.FileOpened != null)
                    this.FileOpened(this, new FileOpenedEventArgs(result, this.ResultKind));
            };
            string resultKindStr = _resultKindStr; // Note: this is here to "capture" the variable so that it can be used inside the "addEventListener" below.

            // Apply the "Filter" property to limit the choice of the user to the specified extensions:
            SetFilter(this.Filter);

            // Listen to the "change" property of the "input" element, and call the callback:
            CSHTML5.Interop.ExecuteJavaScript(@"
                $0.addEventListener(""change"", function(e) {
                    if(!e) {
                      e = window.event;
                    }
                    var fullPath = $0.value;
                    var filename = '';
                    if (fullPath) {
                        var startIndex = (fullPath.indexOf('\\') >= 0 ? fullPath.lastIndexOf('\\') : fullPath.lastIndexOf('/'));
                        filename = fullPath.substring(startIndex);
                        if (filename.indexOf('\\') === 0 || filename.indexOf('/') === 0) {
                            filename = filename.substring(1);
                        }
                    }
                    var input = e.target;
                    var file = input.files[0];
                    var reader = new FileReader();
                    reader.onload = function() {
                      var callback = $1;
                      var result = reader.result;
                      //if (file.type == 'text/plain')
                      callback(result);
                    };
                    var resultKind = $3;
                    if (resultKind == 'DataURL') {
                      reader.readAsDataURL(file);
                    }
                    else {
                      reader.readAsText(file);
                    }
                    var isRunningInTheSimulator = $2;
                    if (isRunningInTheSimulator) {
                        alert(""The file open dialog is not supported in the Simulator. Please test in the browser instead."");
                    }
                });", inputElement, onFileOpened, CSHTML5.Interop.IsRunningInTheSimulator, resultKindStr);
        }

        void SetFilter(string filter)
        {
            // Get the reference to the "input" element:
            var inputElement = CSHTML5.Interop.GetDiv(this);

            // Process the filter list to convert the syntax from XAML to HTML5:
            // Example of syntax in Silverlight: Image Files (*.bmp, *.jpg)|*.bmp;*.jpg|All Files (*.*)|*.*
            // Example of syntax in HTML5: .gif, .jpg, .png, .doc
            string[] splitted = filter.Split('|');
            List<string> itemsKept = new List<string>();
            if (splitted.Length == 1)
            {
                itemsKept.Add(splitted[0]);
            }
            else
            {
                for (int i = 1; i < splitted.Length; i += 2)
                {
                    itemsKept.Add(splitted[i]);
                }
            }
            string filtersInHtml5 = String.Join(",", itemsKept).Replace("*", "").Replace(";", ",");

            // Apply the filter:
            if (!string.IsNullOrWhiteSpace(filtersInHtml5))
            {
                CSHTML5.Interop.ExecuteJavaScript(@"$0.accept = $1", inputElement, filtersInHtml5);
            }
            else
            {
                CSHTML5.Interop.ExecuteJavaScript(@"$0.accept = """"", inputElement);
            }
        }


        public string Filter
        {
            get { return (string)GetValue(FilterProperty); }
            set { SetValue(FilterProperty, value); }
        }
        public static readonly DependencyProperty FilterProperty =
            DependencyProperty.Register("Filter", typeof(string), typeof(ControlForDisplayingTheFileOpenDialog), new PropertyMetadata("", Filter_Changed));

        static void Filter_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var control = (ControlForDisplayingTheFileOpenDialog)d;
            if (CSharpXamlForHtml5.DomManagement.IsControlInVisualTree(control))
            {
                control.SetFilter((e.NewValue ?? "").ToString());
            }
        }
    }

    public class FileOpenedEventArgs : EventArgs
    {
        /// <summary>
        /// Only available if the property "ResultKind" was set to "Text"
        /// </summary>
        [Obsolete]
        public readonly string PlainTextContent;

        /// <summary>
        /// Only available if the property "ResultKind" was set to "Text".
        /// </summary>
        public readonly string Text;

        /// <summary>
        /// Only available if the property "ResultKind" was set to "DataURL".
        /// </summary>
        public readonly string DataURL;

        public FileOpenedEventArgs(object result, ResultKind resultKind)
        {
            if (resultKind == ResultKind.Text)
            {
                this.Text = this.PlainTextContent = (result ?? "").ToString();
            }
            else if (resultKind == ResultKind.DataURL)
            {
                this.DataURL = (result ?? "").ToString();
            }
            else
            {
                throw new NotSupportedException();
            }
        }
    }

    public enum ResultKind
    {
        Text, DataURL
    }
}



The extension works by adding an <input type='file'> tag to the HTML DOM, which will cause the "Open file..." dialog to appear, and by listening to its "change" event in order to get a reference to the selected file. it returns a JavaScript blob. If the file is a plain text file, it will also return a string with the text content of the file.


To use the code:
- Add the following control to your XAML:

Code: Select all

<Page
    ...
    xmlns:extensions="using:CSHTML5.Extensions.FileOpenDialog"
    ...
>
    <Canvas>
        <extensions:ControlForDisplayingTheFileOpenDialog FileOpened="OnFileOpened" Filter="*.txt,*.csv" ResultKind="Text"/>
    </Canvas>
</Page>

- Add the following code to your C# (code-behind):

Code: Select all

async void OnFileOpened(object sender, CSHTML5.Extensions.FileOpenDialog.FileOpenedEventArgs e)
{
    MessageBox.Show(e.Text);
}



If you wish to load a picture file, add an <Image x:Name="MyImageControl"/> control to your XAML, and use the following code (it requires v1.0 Beta 12.6 or newer):
- XAML:

Code: Select all

<extensions:ControlForDisplayingTheFileOpenDialog FileOpened="OnFileOpened" Filter=" (.jpg;*.png)|*.jpg;*.png|All Files (*.*)|*.*" ResultKind="DataURL"/>

- Add the following code to your C# (code-behind):

Code: Select all

async void OnFileOpened(object sender, CSHTML5.Extensions.FileOpenDialog.FileOpenedEventArgs e)
{
   string dataURL = e.DataURL;
   BitmapImage bitmapimage = new BitmapImage();
   bitmapimage.SetSource(dataURL );
   MyImageControl.Source = bitmapimage;
}



If you wish to display a ChildWindow with an "Open file" button, create a new CSHTML5 UserControl named "OpenDialogChildWindow" and use the following code:

- XAML code:

Code: Select all

<ChildWindow
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Width="350"
            Height="240"
            xmlns:fileopendialog="clr-namespace:CSHTML5.Extensions.FileOpenDialog"
            x:Class="CSHTML5.Extensions.FileOpenDialog.OpenDialogChildWindow">
    <StackPanel Margin="30">
        <TextBlock Text="To open a file, click the following button:" TextWrapping="Wrap"/>
        <fileopendialog:ControlForDisplayingTheFileOpenDialog x:Name="FileOpenDialog" Margin="0,20,0,0" Filter=""/>
        <Button Content="OK" HorizontalAlignment="Center" Margin="0,20,0,0" Click="ButtonOK_Click"/>
    </StackPanel>
</ChildWindow>


-C# code:

Code: Select all

using System.Windows;
using System.Windows.Controls;

namespace CSHTML5.Extensions.FileOpenDialog
{
    public partial class OpenDialogChildWindow : ChildWindow
    {
        public OpenDialogChildWindow()
        {
            InitializeComponent();

            this.Title = "Open File";
            this.FileOpenDialog.FileOpened += FileOpenDialog_FileOpened;
        }

        void FileOpenDialog_FileOpened(object sender, CSHTML5.Extensions.FileOpenDialog.FileOpenedEventArgs e)
        {
            this.Result = e;
        }

        public CSHTML5.Extensions.FileOpenDialog.ResultKind ResultKind
        {
            get { return this.FileOpenDialog.ResultKind; }
            set { this.FileOpenDialog.ResultKind = value; }
        }

        public CSHTML5.Extensions.FileOpenDialog.FileOpenedEventArgs Result { get; private set; }

        public string Filter
        {
            get { return FileOpenDialog.Filter; }
            set { FileOpenDialog.Filter = value; }
        }

        private void ButtonOK_Click(object sender, RoutedEventArgs e)
        {
            if (this.Result != null)
            {
                this.DialogResult = true;
            }
            else
            {
                this.DialogResult = false;
            }
        }
    }
}



For security reason, the user needs to click the control in order for the Open File Dialog to appear.

You can also see an example of use with binary content by looking at the ZipFile Extension.

Regards,
JS-Support

Accessing the camera

Posted: Mon Apr 04, 2016 4:53 am
by rkmore
To access the camera using this extension change:

Code: Select all

            CSharpXamlForHtml5.DomManagement.SetHtmlRepresentation(this,
                "<input type='file'>");


to

Code: Select all

            CSharpXamlForHtml5.DomManagement.SetHtmlRepresentation(this,
                "<input type='file' accept='image/*' capture='camera'>");


RKM

Re: FileOpenDialog Extension for CSHTML5

Posted: Mon Apr 04, 2016 9:06 am
by JS-Support @Userware
Thanks a lot RKM for your contribution.

Re: FileOpenDialog Extension for CSHTML5

Posted: Wed Sep 13, 2017 1:36 am
by JS-Support @Userware
UPDATE: The FileOpenDialog extension has just been updated with the following features:
- Improved cross-browser compatibility.

Regards,
JS-Support

Re: FileOpenDialog Extension for CSHTML5

Posted: Tue Sep 26, 2017 9:15 am
by TaterJuice
Non-Official Fork/Update by TaterJuice
(Edit: Removed, please see Update #2, below. Fixed a crucial bug with getting the Base64 Encoded Content)

Re: FileOpenDialog Extension for CSHTML5

Posted: Tue Sep 26, 2017 11:36 pm
by JS-Support @Userware
Thanks a lot TaterJuice for the update!

Re: FileOpenDialog Extension for CSHTML5

Posted: Sun Oct 15, 2017 2:27 pm
by TaterJuice
Non-Official Fork by TaterJuice
Update #2

Description:
I misunderstood the original implementation of the JS FileReader by JS-Admin and consequently, my JavascriptBlobToBase64StringConverter was not working.
This update will read the file content as base64 encoded string with basic mime-type metadata, and NO LONGER attempts to read the file as text.

Changelog:
1. Removed JavasCriptBlobToBase64StringConverter
2. Replaced default implementation of OpenFile dialogue, from ReadAsText to ReadAsDataURL (and fixed base64string implementation)
3. Modified FileOpenedEventArgs, removed PlainTextContent property, Added base64EncodedContentMetaData (JS DataURL Mime-Type string)

args.base64EncodedContentMetaData property format looks like this: "data:image/png;base64"
For more, see https://developer.mozilla.org/en-US/docs/Web/API/FileReader

Code: Select all

//SEE BOTTOM FOR NEW IMPLEMENTATION

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows;
#if SLMIGRATION
using System.Windows;
using System.Windows.Controls;
#else
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
#endif

//------------------------------------
// This is an extension for C#/XAML for HTML5 (www.cshtml5.com)
//
// It requires Beta 7.2 or newer.
//
// It adds the ability to let the user pick a file using the
// "Open File..." dialog of the browser.
//
// It can also be used for example in conjunction with the ZipFile
// Extension to load a ZIP file. Note: if you are looking for
// a way to save files to disk, you can use the FileSaver Extension.
//
// The extension works by adding an <input type='file'> tag to the
// HTML DOM, which will cause the "Open file..." dialog to appear,
// and by listening to its "change" event in order to get a reference
// to the selected file. it returns a JavaScript blob. If the file
// is a plain text file, it will also return a string with the text
// content of the file.
//
// This extension is licensed under the open-source MIT license:
// https://opensource.org/licenses/MIT
//
// Copyright 2016 Userware / CSHTML5
//------------------------------------

/*
Edit by TaterJuice, 9-26-2017
Changelog:
1. Added "FileOpenedEventArgs.FileName (string)" property to class FileOpenedEventArgs
2. Added class JSBlobToBase64StringConverter to convert file content to base64 string (so the content can be used in a multi-part post, or transmitted to a webservice as a base64 encoded string)

Edit by TaterJuice, 10-15-2017
Changelog:
1. Removed JavasCriptBlobToBase64StringConverter
2. Replaced default implementation of OpenFile dialogue, from ReadAsText to ReadAsDataURL (and fixed base64string implementation)
3. Modified FileOpenedEventArgs, removed PlainTextContent property, Added base64EncodedContentMetaData (JS DataURL Mime-Type string)

args.base64EncodedContentMetaData property format looks like this: "data:image/png;base64"
For more, see https://developer.mozilla.org/en-US/docs/Web/API/FileReader

namespace CSHTML5.Extensions.FileOpenDialog
{
    public class ControlForDisplayingTheFileOpenDialog : Control
    {
        public event EventHandler<FileOpenedEventArgs> FileOpened;

        public ControlForDisplayingTheFileOpenDialog()
        {
            CSharpXamlForHtml5.DomManagement.SetHtmlRepresentation(this,
                "<input type='file' style='font-size: 0px; width: 100%; height: 100%; opacity: 0; filter: alpha(opacity = 0);' />");

            this.Loaded += ControlForDisplayingAFileOpenDialog_Loaded;
        }
        void ControlForDisplayingAFileOpenDialog_Loaded(object sender, RoutedEventArgs e)
        {
            // Get the reference to the "input" element:
            var inputElement = CSHTML5.Interop.GetDiv(this);

            Action<string, string, string> onFileOpened = (base64encodedContent, mimetypeString, filename) =>
            {
                if (this.FileOpened != null)
                    this.FileOpened(this, new FileOpenedEventArgs(base64encodedContent, mimetypeString, filename));
            };

            // Apply the "Filter" property to limit the choice of the user to the specified extensions:
            SetFilter(this.Filter);

            // Listen to the "change" property of the "input" element, and call the callback:
            CSHTML5.Interop.ExecuteJavaScript(@"
                $0.addEventListener(""change"", function(e){
                    if(!e){
                      e = window.event;
                    }
                    var fullPath = $0.value;
                    var filename = '';
                    if (fullPath) {
                        var startIndex = (fullPath.indexOf('\\') >= 0 ? fullPath.lastIndexOf('\\') : fullPath.lastIndexOf('/'));
                        filename = fullPath.substring(startIndex);
                        if (filename.indexOf('\\') === 0 || filename.indexOf('/') === 0) {
                            filename = filename.substring(1);
                        }
                    }
                    var input = e.target;
                    var file = input.files[0];
                    var reader = new FileReader();
                    reader.onload = function(){
                      var dataUrl = reader.result;
                      var mimeTypeString = dataUrl.split(',')[0];
                      var base64 = dataUrl.split(',')[1];
                      $1(base64, mimeTypeString, filename);
                    };
                    //reader.readAsText(file);
                    reader.readAsDataURL(file);
                    var isRunningInTheSimulator = $2;
                    if (isRunningInTheSimulator)
                        alert(""The file open dialog is not supported in the Simulator. Please test in the browser instead."");
                });", inputElement, onFileOpened, CSHTML5.Interop.IsRunningInTheSimulator);
        }

        void SetFilter(string filter)
        {
            // Get the reference to the "input" element:
            var inputElement = CSHTML5.Interop.GetDiv(this);

            // Process the filter list to convert the syntax from XAML to HTML5:
            // Example of syntax in Silverlight: Image Files (*.bmp, *.jpg)|*.bmp;*.jpg|All Files (*.*)|*.*
            // Example of syntax in HTML5: .gif, .jpg, .png, .doc
            string[] splitted = filter.Split('|');
            List<string> itemsKept = new List<string>();
            if (splitted.Length == 1)
            {
                itemsKept.Add(splitted[0]);
            }
            else
            {
                for (int i = 1; i < splitted.Length; i += 2)
                {
                    itemsKept.Add(splitted[i]);
                }
            }
            string filtersInHtml5 = String.Join(",", itemsKept).Replace("*", "").Replace(";", ",");

            // Apply the filter:
            if (!string.IsNullOrWhiteSpace(filtersInHtml5))
            {
                CSHTML5.Interop.ExecuteJavaScript(@"$0.accept = $1", inputElement, filtersInHtml5);
            }
            else
            {
                CSHTML5.Interop.ExecuteJavaScript(@"$0.accept = """"", inputElement);
            }
        }

        public string Filter
        {
            get { return (string)GetValue(FilterProperty); }
            set { SetValue(FilterProperty, value); }
        }
        public static readonly DependencyProperty FilterProperty =
            DependencyProperty.Register("Filter", typeof(string), typeof(ControlForDisplayingTheFileOpenDialog), new PropertyMetadata("", Filter_Changed));

        static void Filter_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var control = (ControlForDisplayingTheFileOpenDialog)d;
            if (CSharpXamlForHtml5.DomManagement.IsControlInVisualTree(control))
            {
                control.SetFilter((e.NewValue ?? "").ToString());
            }
        }
    }

    public class FileOpenedEventArgs : EventArgs
    {
        public readonly string FileName;

        public readonly string base64EncodedContent;

        public readonly string base64EncodedContentMetaData;

        public FileOpenedEventArgs(string base64content, string mimetypeString, string filename)
        {
            this.FileName = filename;
            this.base64EncodedContent = base64content;
            this.base64EncodedContentMetaData = mimetypeString;
        }
    }
}




Now you can use the control like this:

Code: Select all

<Page
    ...
    xmlns:extensions="using:CSHTML5.Extensions.FileOpenDialog"
    ...
>
    <Canvas>
        <extensions:ControlForDisplayingTheFileOpenDialog FileOpened="OnFileOpened" Filter="*.txt,*.csv"/>
    </Canvas>
</Page>


And add this to your code-behind:

Code: Select all

async void OnFileOpened(object sender, CSHTML5.Extensions.FileOpenDialog.FileOpenedEventArgs e)
{
    MessageBox.Show(String.Format("Filename = {0}", e.FileName));
    MessageBox.Show(String.Format("MimeTypeString = {0}", e.base64EncodedContentMetaData));
    MessageBox.Show(String.Format("Base64 Content = {0}", e.base64EncodedContent));
}

Re: FileOpenDialog Extension for CSHTML5

Posted: Sun Oct 15, 2017 11:46 pm
by JS-Support @Userware
Thanks a lot TaterJuice!

Re: FileOpenDialog Extension for CSHTML5

Posted: Thu Nov 16, 2017 8:46 am
by JS-Support @Userware
UPDATE: The FileOpenDialog code of the original post has just been updated with the following changes:
- Added support for getting a "DataURI" from a binary file (useful for reading pictures for example) (thanks to TaterJuice)
- Added example of loading a picture (requires v1.0 Beta 12.6 or newer)
- Added "FileName" property (thanks to TaterJuice)
- Added code for displaying an "OpenFile" ChildWindow

Thanks.
Regards,
JS-Support

Re: FileOpenDialog Extension for CSHTML5 [BUG]

Posted: Thu Apr 04, 2019 9:10 pm
by fangeles
The web browser is detected as "Simulator" by the OpenFileDialog in Bridge version.


Is it on the CSHTML5 v2.0 or the FileOpenDialog Extension?

Thanks.

Re: FileOpenDialog Extension for CSHTML5

Posted: Tue May 07, 2019 9:21 am
by rthomas
Hi,

Another fork with XAML / Web compatible code.
This mean you can use it to create exe or html version and it behaves the same.

XAML didn't change.

Remi

Code: Select all

using System;
using System.Collections.Generic;
#if !CSHTML5
using System.Windows;
using System.Windows.Controls;
#else
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
#endif

//------------------------------------
// This is an extension for C#/XAML for HTML5 (www.cshtml5.com)
//
// It requires Beta 7.2 or newer.
//
// It adds the ability to let the user pick a file using the
// "Open File..." dialog of the browser.
//
// It can also be used for example in conjunction with the ZipFile
// Extension to load a ZIP file. Note: if you are looking for
// a way to save files to disk, you can use the FileSaver Extension.
//
// The extension works by adding an <input type='file'> tag to the
// HTML DOM, which will cause the "Open file..." dialog to appear,
// and by listening to its "change" event in order to get a reference
// to the selected file. it returns a JavaScript blob. If the file
// is a plain text file, it will also return a string with the text
// content of the file.
//
// This extension is licensed under the open-source MIT license:
// https://opensource.org/licenses/MIT
//
// Copyright 2017 Userware / CSHTML5
//------------------------------------

namespace qfWeb
{
    public enum ResultKind
    {
        Text, DataURL
    }
    public class ControlForDisplayingTheFileOpenDialog : UserControl
    {
        public event EventHandler<FileOpenedEventArgs> FileOpened;

        private ResultKind _resultKind;
        private string _resultKindStr;
        public ResultKind ResultKind
        {
            get { return _resultKind; }
            set
            {
                _resultKind = value;
                _resultKindStr = value.ToString();
            }
        }

        public ControlForDisplayingTheFileOpenDialog()
        {
            this.ResultKind = ResultKind.Text; //Note: this is to set the default value of the property.
#if CSHTML5
            CSharpXamlForHtml5.DomManagement.SetHtmlRepresentation(this,
                "<input type='file'>");
#endif
            this.Loaded += ControlForDisplayingAFileOpenDialog_Loaded;
        }

#if CSHTML5
        public string Filter
        {
            get { return (string)GetValue(FilterProperty); }
            set { SetValue(FilterProperty, value); }
        }
        void ControlForDisplayingAFileOpenDialog_Loaded(object sender, RoutedEventArgs e)
        {
            // Get the reference to the "input" element:
            var inputElement = CSHTML5.Interop.GetDiv(this);

            Action<object> onFileOpened = (result) =>
            {
                if (this.FileOpened != null)
                    this.FileOpened(this, new FileOpenedEventArgs(result, this.ResultKind));
            };
            string resultKindStr = _resultKindStr; // Note: this is here to "capture" the variable so that it can be used inside the "addEventListener" below.

            // Apply the "Filter" property to limit the choice of the user to the specified extensions:
            SetFilter(this.Filter);

            // Listen to the "change" property of the "input" element, and call the callback:
            CSHTML5.Interop.ExecuteJavaScript(@"
                $0.addEventListener(""change"", function(e) {
                    if(!e) {
                      e = window.event;
                    }
                    var fullPath = $0.value;
                    var filename = '';
                    if (fullPath) {
                        var startIndex = (fullPath.indexOf('\\') >= 0 ? fullPath.lastIndexOf('\\') : fullPath.lastIndexOf('/'));
                        filename = fullPath.substring(startIndex);
                        if (filename.indexOf('\\') === 0 || filename.indexOf('/') === 0) {
                            filename = filename.substring(1);
                        }
                    }
                    var input = e.target;
                    var file = input.files[0];
                    var reader = new FileReader();
                    reader.onload = function() {
                      var callback = $1;
                      var result = reader.result;
                      //if (file.type == 'text/plain')
                      callback(result);
                    };
                    var resultKind = $3;
                    if (resultKind == 'DataURL') {
                      reader.readAsDataURL(file);
                    }
                    else {
                      reader.readAsText(file);
                    }
                    var isRunningInTheSimulator = $2;
                    //if (isRunningInTheSimulator) {
                    //    alert(""The file open dialog is not supported in the Simulator. Please test in the browser instead."");
                    //}
                });", inputElement, onFileOpened, CSHTML5.Interop.IsRunningInTheSimulator, resultKindStr);
        }

        void SetFilter(string filter)
        {
            // Get the reference to the "input" element:
            var inputElement = CSHTML5.Interop.GetDiv(this);

            // Process the filter list to convert the syntax from XAML to HTML5:
            // Example of syntax in Silverlight: Image Files (*.bmp, *.jpg)|*.bmp;*.jpg|All Files (*.*)|*.*
            // Example of syntax in HTML5: .gif, .jpg, .png, .doc
            string[] splitted = filter.Split('|');
            List<string> itemsKept = new List<string>();
            if (splitted.Length == 1)
            {
                itemsKept.Add(splitted[0]);
            }
            else
            {
                for (int i = 1; i < splitted.Length; i += 2)
                {
                    itemsKept.Add(splitted[i]);
                }
            }
            string filtersInHtml5 = String.Join(",", itemsKept).Replace("*", "").Replace(";", ",");

            // Apply the filter:
            if (!string.IsNullOrWhiteSpace(filtersInHtml5))
            {
                CSHTML5.Interop.ExecuteJavaScript(@"$0.accept = $1", inputElement, filtersInHtml5);
            }
            else
            {
                CSHTML5.Interop.ExecuteJavaScript(@"$0.accept = """"", inputElement);
            }
        }

        public static readonly DependencyProperty FilterProperty =
            DependencyProperty.Register("Filter", typeof(string), typeof(ControlForDisplayingTheFileOpenDialog), new PropertyMetadata("", Filter_Changed));

        static void Filter_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var control = (ControlForDisplayingTheFileOpenDialog)d;
            if (CSharpXamlForHtml5.DomManagement.IsControlInVisualTree(control))
            {
                control.SetFilter((e.NewValue ?? "").ToString());
            }
        }
#else
        public string Filter { get; set; }

        TextBlock m_fileName = new TextBlock() { Text = "No file chosen", Margin = new Thickness(5,0,0,0) };
        void ControlForDisplayingAFileOpenDialog_Loaded(object sender, RoutedEventArgs e)
        {
            StackPanel sp = new StackPanel() { Orientation = Orientation.Horizontal };
            Button b = new Button();
            TextBlock label = new TextBlock() { Text = "Choose file" };
            b.Content = label;
            b.Click += B_Click;
            sp.Children.Add(b);
            sp.Children.Add(m_fileName);
            AddChild(sp);
        }

        private void B_Click(object sender, RoutedEventArgs e)
        {
            string[] filters = Filter.Split(',');
            string newFilter = "";
            string sep = "";
            foreach (string f in filters)
            {
                newFilter += sep + f.Replace("*.", "") + " files|" + f;
                sep = "|";
            }
            // newFilter = "Image files (*.bmp, *.jpg)|*.bmp;*.jpg|All files (*.*)|*.*";
            Microsoft.Win32.OpenFileDialog ofd = new Microsoft.Win32.OpenFileDialog() { Filter = newFilter };
            if (ofd.ShowDialog().GetValueOrDefault())
            {
                if (FileOpened != null)
                {
                    m_fileName.Text = System.IO.Path.GetFileName(ofd.FileName);
                    FileOpenedEventArgs foea = null;
                    if (ResultKind == ResultKind.Text)
                        foea = new FileOpenedEventArgs(System.IO.File.ReadAllText(ofd.FileName), ResultKind);
                    else if (ResultKind == ResultKind.DataURL)
                        foea = new FileOpenedEventArgs(Convert.ToBase64String(System.IO.File.ReadAllBytes(ofd.FileName)), ResultKind);
                    FileOpened(this, foea);
                }
            }
        }
#endif
    }
    public class FileOpenedEventArgs : EventArgs
    {
        /// <summary>
        /// Only available if the property "ResultKind" was set to "Text".
        /// </summary>
        public readonly string Text;

        /// <summary>
        /// Only available if the property "ResultKind" was set to "DataURL".
        /// </summary>
        public readonly string DataURL;

        public FileOpenedEventArgs(object result, ResultKind resultKind)
        {
            if (resultKind == ResultKind.Text)
            {
                this.Text = (result ?? "").ToString();
            }
            else if (resultKind == ResultKind.DataURL)
            {
                this.DataURL = (result ?? "").ToString();
            }
            else
            {
                throw new NotSupportedException();
            }
        }
    }
}

Re: FileOpenDialog Extension for CSHTML5

Posted: Mon May 13, 2019 2:27 pm
by ScottM
I've been playing with implementing this, and noticed an oddity, I think. With all three sets of code (original revised, TaterJuice revised, and rthomas version), when I run in the simulator (2.0.0-alpha25-040), the filter is set properly:

FileOpenSimulator.png
FileOpenSimulator.png (50.07 KiB) Viewed 30087 times


But when I run in the browser (in my case, Edge), the filter is not respected.

FileOpenEdge.png
FileOpenEdge.png (70.08 KiB) Viewed 30087 times


Have I blundered? Or is Edge a problem?

Thanks in advance for any pointers.

Re: FileOpenDialog Extension for CSHTML5

Posted: Fri May 17, 2019 12:06 pm
by ScottM
Following on to the above, I do, filter issue notwithstanding, have the file contents available.

Now, what to do with it? I'd like, if possible, to do two things:

1. Send an email with form responses and the uploaded file attached.

2. Save the uploaded file and form responses to a folder on the server.

I realize I'm asking for more server-side integration than the thick client, but I thought I would find examples here in the forum on how to do that. I ran several searches, but came up empty.

Any suggestions?

ETA: I looked for MailMessage that's in the WPF stack, but no dice here, unless I am missing something.