Busyindicator на WPF для WinForms

Необходимо WPF Busyindicator, созданный в Expression blend, размещать на WinForms или Telerik -форме. При этом необходимо предоставить возможность делать активными любые контролы, расположенные на форме. Должно получиться следующее:

Программирование: Busyindicator на WPF для WinForms

Для начала можно попробовать решить задачу в лоб: берем System.Windows.Forms.Integration.ElementHost, кидаем на форму, в Select Hosted Content указываем наш индикатор.
Как результат:

Программирование: Busyindicator на WPF для WinForms

Вполне ожидаемо, но неудовлетворительно.
Предлагаю своё решение:
Для начала нам понадобится user control для WinForms, который мы будем кидать на формы.
Для того чтобы добиться эффекта прозрачности я решил делать снимок клиентской части формы (без рамок). Эту обязанность выполняет метод GetImage класса BusyControl. Если на форме есть элемент, который должен быть активным, например кнопка отмены, то нужно передать её в метод ControlsToEnable.
<code class="cs">public partial class BusyControl : UserControl
{
    private bool _isBusy;
    const int BorderSize = 8;
    const int TopBorderSize = 30;

    public BusyControl()
    {
        InitializeComponent();
    }

    public bool IsBusy
    {
        get { return _isBusy; }
        set
        {
            _isBusy = value;
            if (_isBusy)
            {
                Init();
                InitControlsAndStart();
            }
            else
                StopIndicator();
        }
    }

    private void StopIndicator()
    {
        SuspendLayout();
        foreach (Control control in Controls)
        {
            if (control.GetType() == typeof(ElementHost))
            {
                Controls.Remove(control);
                control.Dispose();
            }
        }
        Dock = DockStyle.None;
        ResumeLayout();
    }

    private void Init()
    {
        SendToBack();
        Dock = DockStyle.Fill;
    }

    /// <summary>
    /// Выводит элементы, которые должны быть активными
    /// </summary>
    /// <param name="controls"></param>
    public void ControlsToEnable(IEnumerable<Control> controls)
    {
        foreach (var control in controls)
        {
            control.BringToFront();
        }
    }

    private void InitControlsAndStart()
    {
        if (DesignMode || Parent == null)
            return;

        var elh = new ElementHost();
        var u = new UserControl1
                    {
                        BusyContentWidth = Width,
                        BusyContentHeight = Height
                    };

        using (var stream = new MemoryStream())
        {
            using (var image = GetImage(Parent))
            {
                image.Save(stream, ImageFormat.Gif);
            }

            var bitmap = new BitmapImage();
            bitmap.BeginInit();
            bitmap.StreamSource = stream;
            bitmap.CacheOption = BitmapCacheOption.OnLoad;
            bitmap.EndInit();
            bitmap.Freeze();

            u.ImageSource = bitmap;
        }

        elh.Child = u;
        Controls.Add(elh);
        elh.Dock = DockStyle.Fill;
        BringToFront();
    }

    public Bitmap GetImage(Control form)
    {
        var image = new Bitmap(Width, Height);
        using (Graphics g = Graphics.FromImage(image))
        {
             g.CopyFromScreen(form.Location, new Point(-BorderSize, -TopBorderSize),
                              new Size(form.Width - BorderSize, form.Height - BorderSize));
        }
        return image;
    }
}
</code>
Дальше с полученным изображением можно сделать что-нибудь интересное, например применить эффект размытия и добавить градиентную заливку. Эти функции будет выполнять WPF user control, куда через dependency property мы будем передавать фоновый рисунок:
<code class="cs">public partial class UserControl1 : UserControl
{
    public UserControl1()
    {
        InitializeComponent();
    }

    #region DependecyProperties

    #region ImageSource

    public static readonly DependencyProperty ImageSourceProperty
        = DependencyProperty.Register("ImageSource", typeof(BitmapImage),
        typeof(UserControl1), new PropertyMetadata(DefaultValueChanged));

    private static void DefaultValueChanged(
        DependencyObject dependencyObject,
        DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
    {
        var userControl1 = (UserControl1)dependencyObject;
        userControl1.img.Source = userControl1.ImageSource;
    }

    public BitmapImage ImageSource
    {
        get { return (BitmapImage)GetValue(ImageSourceProperty); }
        set { SetValue(ImageSourceProperty, value); }
    }

    #endregion ImageSource

    //#region BusyContentHeight 
    //#region BusyContentWidth

    #endregion //DependecyProperties
}  
</code>

Все визуальные эффекты создаются в XAML нашего UserControl1 и там же прикручивается, сделанный заранее, busyindicator:
<code class="xml"><Grid>
    <Grid x:Name="BackgroundContainer" >
        <Image x:Name="img"/>
    </Grid> 
    <Grid x:Name="BusyContent" Cursor="None" RenderTransformOrigin="0.5,0.5">
        <!--Здесь накручиваем визуальные эффекты-->
        <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
            <converter:ScoutIndicator />
        </StackPanel>
    </Grid>
</Grid>
</code>

Чтобы busyindicator заработал нужно выставить значение свойства IsBusy в true:

<code class="cs">private void buttonStartClick(object sender, EventArgs e)
{
    busyControl1.IsBusy = true;
    busyControl1.ControlsToEnable(new List<Control> { buttonCancel })
}
</code>


0 комментариев

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.