۱۳۹۰/۰۹/۰۸

استفاده از كنترل‌هاي Active-X در WPF


گاهي از اوقات شايد نياز شود تا از يك كنترل Active-X در WPF استفاده شود؛ مثلا هيچ نمايش دهنده‌ي PDF ايي را در ويندوز نمي‌توان يافت كه امكانات و كيفيت آن در حد Acrobat reader و Active-X آن باشد. يك روش استفاده از آن‌را به كمك كنترل WebBrowser در WPF پيشتر در اين سايت مطالعه كرده‌ايد. روش معرفي شده براي WinForm هم در WPF قابل استفاده است كه در ادامه شرح آ‌ن‌ خواهد آمد.

الف) بجاي اضافه كردن يك User control مخصوص WPF يك user control از نوع WinForms را به يك پروژه WPF اضافه كنيد.


سپس مراحل مشابهي را مانند حالت WinForms، بايد طي كرد:
ب) در VS.NET‌ از طريق منوي Tools گزينه‌ي Choose toolbox items ، برگه‌ي Com components را انتخاب كنيد.
ج) سپس گزينه‌ي Adobe PDF reader را انتخاب نمائيد و بر روي دكمه‌ي OK‌ كليك كنيد.


د) اكنون اين كنترل جديد را بر روي فرم user control قسمت الف برنامه قرار دهيد. به صورت خودكار COMReference هاي متناظر هم به پروژه اضافه مي‌شوند.
پس از اينكه كنترل بر روي فرم قرار گرفت بهتر است به خواص آن مراجعه كرده و خاصيت Dock آن‌را با Fill مقدار دهي كرد تا كنترل به صورت خودكار در هر اندازه‌اي كل ناحيه‌ي متناظر را پوشش دهد.


كد‌هاي مرتبط با نمايش فايل PDF اين كنترل هم به شرح زير است:

using System.Windows.Forms;

namespace WpfPdfViewer.Controls
{
    public partial class AcroReader : UserControl
    {
        public AcroReader(string fileName)
        {
            InitializeComponent();
            ShowPdf(fileName);
        }

        public void ShowPdf(string fileName)
        {
            if (string.IsNullOrWhiteSpace(fileName)) return;
            axAcroPDF1.LoadFile(fileName);
            axAcroPDF1.setShowToolbar(true);
            axAcroPDF1.Show();
        }
    }
}


خوب، ما تا اينجا يك كنترل Active-X را از طريق يك User controls مخصوص WinForms به پروژه‌ي WPF جاري اضافه كرده‌ايم. براي اينكه بتوانيم اين كنترل را درون مثلا يك User control از جنس WPF و XAML نمايش دهيم بايد از كنترل WindowsFormsHost استفاده كرد. براي اين منظور نياز است تا ارجاعي را به اسمبلي WindowsFormsIntegration اضافه كنيم. پس از آن كنترل ياد شده قابل استفاده خواهد بود.


براي نمونه كدهاي XAML پنجره اصلي برنامه مي‌تواند به صورت زير باشد:

<Window x:Class="WpfPdfViewer.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <WindowsFormsHost x:Name="WindowsFormsHost1" />
    </Grid>
</Window>

سپس جهت استفاده از كنترل WindowsFormsHost خواهيم داشت:

using WpfPdfViewer.Controls;

namespace WpfPdfViewer
{
    public partial class MainWindow
    {
        public MainWindow()
        {
            InitializeComponent();
            WindowsFormsHost1.Child = new AcroReader(@"PageSummary.pdf");
        }
    }
}

فقط كافي است شيء Child اين كنترل را با وهله‌اي از يوزركنترل AcroReader اضافه شده به برنامه مقدار دهي كنيم.

سؤال: اين روش زياد MVVM friendly نيست. به عبارتي Child را نمي‌توان از طريق Binding مقدار دهي كرد. آيا راهي براي آن وجود دارد؟
پاسخ: بله. روش متداول براي حل اين نوع مشكلات، نوشتن يك DependencyObject و Attached property مناسب مي‌باشد كه به آن‌ها Behaviors هم مي‌گويند. براي مثال يك نمونه از اين پياده سازي را در ذيل مشاهده مي‌كنيد:

using System;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Forms.Integration;

namespace WpfPdfViewer.Behaviors
{
    public class WindowsFormsHostBehavior : DependencyObject
    {
        public static readonly DependencyProperty BindableChildProperty =
                                    DependencyProperty.RegisterAttached("BindableChild",
                                    typeof(Control),
                                    typeof(WindowsFormsHostBehavior),
                                    new UIPropertyMetadata(null, BindableChildPropertyChanged));

        public static Control GetBindableChild(DependencyObject obj)
        {
            return (Control)obj.GetValue(BindableChildProperty);
        }

        public static void SetBindableChild(DependencyObject obj, Control value)
        {
            obj.SetValue(BindableChildProperty, value);
        }

        public static void BindableChildPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            var windowsFormsHost = o as WindowsFormsHost;
            if (windowsFormsHost == null)
                throw new InvalidOperationException("This behavior can only be attached to a WindowsFormsHost.");

            var control = (Control)e.NewValue;
            windowsFormsHost.Child = control;
        }
    }
}

كه نهايتا براي استفاده از آن خواهيم داشت:

<WindowsFormsHost 
       Behaviors:WindowsFormsHostBehavior.BindableChild="{Binding ...}" />

و در ViewModel برنامه هم مانند مثال فوق، فقط كافي است يك وهله از new AcroReader به اين خاصيت قابل انقياد از نوع Control، انتساب داده شود.
يا حتي مي‌توان بجاي نوشتن يك BindableChild، براي مثال مسير فايل pdf را به DependencyObject تعريف شده ارسال كرد و سپس در همانجا اين وهله سازي و انتسابات صورت گيرد (بجاي ViewModel برنامه كه اينبار فقط مسير را تنظيم مي‌كند).