Silverlight BusyIndicator : Higher Z-Index than al

2019-05-29 18:59发布

问题:

Perhaps the title is worded incorrectly.

I have a "global" Busy Indicator that works great, as long as I don't try to use it when a ChildWindow is open.

I access the "global" Busy Indicator by using a static method in my App.xaml.cs:

BusyIndicator b = (BusyIndicator)App.Current.RootVisual;
if (b != null)
{
    b.BusyContent = busyText;
    b.IsBusy = true;
}

However, if a ChildWindow is open, the BusyIndicator is always behind it.

I thought that I could set b.Content = VisualTreeHelper.GetOpenPopups().First(), but that didn't work either.

Does anyone have any tips on having a BusyIndicator on top of open ChildWindows?

Thanks in advance.

UPDATE (SOLUTION)

Dave S sent me on the right track. It was more complicated than I had hoped, but here's my solution.

First, I had to make a full style for the ChildWindow, copying all of the Template style (left some out for post-size reasons):

<Style x:Key="MyChildWindowStyle" TargetType="gs:MyChildWindow">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="gs:MyChildWindow">
                <toolkit:BusyIndicator IsBusy="{TemplateBinding IsBusy}" BusyContent="{TemplateBinding BusyContent}" BusyContentTemplate="{StaticResource MyBusyIndicatorDataTemplate}">
                    <Grid>
                        <ContentPresenter x:Name="ContentPresenter" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                    </Grid>
                </toolkit:BusyIndicator>
</Style>

Then, I created my base class; notice the constructor sets the style. (Errors occurred if I tried to make it abstract.)

public class MyChildWindow : ChildWindow
{
    public static readonly DependencyProperty IsBusyProperty = DependencyProperty.Register("IsBusy", typeof(bool), typeof(MyChildWindow), null);
    public static readonly DependencyProperty BusyContentProperty = DependencyProperty.Register("BusyContent", typeof(object), typeof(MyChildWindow), null);

    public bool IsBusy
    {
        get { return (bool)GetValue(IsBusyProperty); }
        set { SetValue(IsBusyProperty, value); }
    }

    public object BusyContent
    {
        get { return GetValue(BusyContentProperty); }
        set { SetValue(BusyContentProperty, value); }
    }

    public MyChildWindow()
    {
        this.Style = Application.Current.Resources["MyChildWindowStyle"] as Style;
    }
}

Make sure to add a new ChildWindow, and change the <controls:ChildWindow to <gs:MyChildWindow (same with the code-behind).

Finally, update the static SetBusyIndicator method:

public static void SetBusyIndicator(string busyText, Uri busyImage)
{
    var op = VisualTreeHelper.GetOpenPopups().Where(o => o.Child is MyChildWindow);
    if (op.Any())
    {
        var bidc = new MyBusyIndicatorDataContext(busyText, busyImage);
        foreach (System.Windows.Controls.Primitives.Popup p in op)
        {
            var c = p.Child as MyChildWindow;
            c.BusyContent = bidc;
            c.IsBusy = true;
        }
    }
    else
    {
        BusyIndicator b = Current.RootVisual as BusyIndicator;
        if (b != null)
        {
            b.BusyContent = new MyBusyIndicatorDataContext(busyText, busyImage);
            b.IsBusy = true;
        }
    }
}

I'm not sure this is the most efficient, but it does seem to work nicely.

回答1:

As you've got your application root visual set to the busy spinner you will not be able to change the Z-Index so it is higher than the ChildWindow. Your best bet would be to extend the ChildWindow control and add a busy spinner into it and then set IsBusy on the ChildWindow rather the root visual when you have windows open.

Hope that helps.



回答2:

You don't have to subclass ChildWindow to be able to use BusyIndicator over ChildWindows. I am using following solution:

1-Define Global ContentTemplate for all ChildWindow Controls. Bind IsBusy property to "IsBusy" of ChildWindow's datacontext.

<Style TargetType="sdk:ChildWindow">
        <Setter Property="ContentTemplate">
            <Setter.Value>
                <DataTemplate>
                    <toolkit:BusyIndicator IsBusy="{Binding IsBusy}">
                        <ContentPresenter Content="{Binding Content, RelativeSource={RelativeSource TemplatedParent}}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                    </toolkit:BusyIndicator>
                </DataTemplate>
            </Setter.Value>
        </Setter>
    </Style>

2- Define a singleton class which is ready during silverlight application is running. I picked App class for this. Implement INotifyPropertyChanged interface to auto refresh all IsBusy binders. Implement IsBusy property. Implement ShowBusyIndicator and HideBusyIndicator methods. In ShowBusyIndicator method iterate all open ChildWindows and update their DataContexts.

public class App : Application, INotifyPropertyChanged
{
    public static BusyIndicator GlobalBusyIndicator { get; private set; }

    private bool _isBusy;
    public bool IsBusy
    {
        get
        {
            return _isBusy;
        }
        set
        {
            _isBusy = value;
            RaisePropertyChanged(new PropertyChangedEventArgs("IsBusy"));
        }
    }

    public static void ShowBusyIndicator()
    {
        var popups = VisualTreeHelper.GetOpenPopups();
        foreach (var p in popups)
        {
            ChildWindow cw = p.Child as ChildWindow;
            if (cw != null)
                cw.DataContext = App.Current;
        }
        (Current as App).IsBusy = true;
        GlobalBusyIndicator.IsBusy = true;
    }

    public static void HideBusyIndicator()
    {
        (Current as App).IsBusy = false;
        GlobalBusyIndicator.IsBusy = false;
    }

    private void Application_Startup(object sender, StartupEventArgs e)
    {
        string baseurl = Host.Source.AbsoluteUri;
        BaseUrl = baseurl.Substring(0, baseurl.IndexOf("ClientBin"));

        GlobalBusyIndicator = new BusyIndicator();
        GlobalBusyIndicator.HorizontalAlignment = HorizontalAlignment.Stretch;
        GlobalBusyIndicator.VerticalAlignment = VerticalAlignment.Stretch;
        GlobalBusyIndicator.Content = new Shell();

        this.RootVisual = GlobalBusyIndicator;
    }

    public event PropertyChangedEventHandler PropertyChanged;

    public void RaisePropertyChanged(PropertyChangedEventArgs e)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) handler(this, e);
    }
}