Gerenciamento de janelas em WPF

Desenvolver uma aplicação comercial utilizando WPF é realmente muito legal! A plataforma nos oferece recursos de DataBinding que facilitam nossa vida como desenvolvedores e que permitem a criação de visualizações magníficas para os usuários finais. Mesmo com tantas facilidades ainda nos esbarramos em conceitos que não existem em WPF, como herança visual de XAML e até mesmo o conceito de MDI.

Nota: Neste post, não entrarei nos detalhes, contudo, o código estará disponível.

Aqui em nossa aplicação um dos requisitos era montar uma interface MDI, que pemitisse que um usuário pudesse abrir várias janelas de forma simultânea e até mesmo a mesma janela mais de uma vez. A solução desevolvida foi a de abstrair o conceito de “janela gerenciada” de tal forma que é possível indicar ao sistema qual janela será gerenciada ou não. Posteriormente foi necessário se criar um mecanismo que mantivesse o estado dessas janelas gerenciadas. Essas janelas são mantidas em uma lista que pode ser exibida ao usuário e ele, ao clicar em um item dessa lista, traz a janela selecionada para o foco ativo. Bem vamos ver algum código. 

Nota: Esta solução que apresento foi inspirada por este post, apresentando um código parecido, contudo foi ajustado para minha realidade… Logo irei postar tudo detalhado no Code Project.

Tenho uma classe que irá gerenciar as janelas, a classe WindowViewStateManager. Ela herda de ViewModel, que é um tipo declarado pelo framework Julmar que facilita o uso padrão M-V-VM. Se você desenvolve em WPF sem utilizar esse padrão (ou algo similar), corra!!! Por que seu código deve estar virando uma bela macarronada!

Nessa classe tenho a lista de janela que foram abertas:

public ObservableCollection<WindowViewStateInstance> WindowViewStates
        {
            get{ return_windowViewStates ?? (_windowViewStates = newObservableCollection<WindowViewStateInstance>()); }
        }

Agora como que uma janela pode ser adicionada à essa lista? Para isso criamos uma Attached Property que inclue esse comportamento em nossas janelas. Ela está definida na classe a seguir:

 

public class WindowViewState
{
    public static bool GetIsManaged(DependencyObject obj)
    {
        return (bool) obj.GetValue(IsManagedProperty);
    }

    public static void SetIsManaged(DependencyObject obj, bool value)
    {
        obj.SetValue(IsManagedProperty, value);
    }

    // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty IsManagedProperty =
        DependencyProperty.RegisterAttached("IsManaged", typeof (bool), typeof (WindowViewState),
                                            new FrameworkPropertyMetadata((bool) false,
                                                                          new PropertyChangedCallback(
                                                                              OnIsManagedChanged)));

    private static void OnIsManagedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        Window target = (Window) d;
        if (target != null)
        {
            target.Loaded += new RoutedEventHandler(target_Loaded);
            target.Closed += new EventHandler(target_Closed);

        }
    }
    

    private static void target_Closed(object sender, EventArgs e)
    {
        Window target = (Window) sender;
        if (target != null)
        {
            WindowViewStateManager.Instance.RemoveWindowViewState(target);
            WindowViewStateManager.Instance.ShowLastWindow();
        }
    }

    private static void target_Loaded(object sender, RoutedEventArgs e)
    {
        Window target = (Window) sender;
        if (target != null)
        {
            WindowViewStateInstance instance = new WindowViewStateInstance();
            instance.Title = target.Title;
            instance.Icon = new VisualBrush(target);
            instance.Window = target;
            instance.Number = (WindowViewStateManager.Instance.CountInstancesForWindowTitle(target.Title) + 1);
            instance.Window.Tag = instance.Number;
            WindowViewStateManager.Instance.HideAllVisible(instance.WindowId);
            WindowViewStateManager.Instance.WindowViewStates.Add(instance);
        }
    }
}

Diferentemente da solução original, o meu requisito exige que mesmo podendo abrir mais de uma janela, apenas uma será visível. Isso eu trato no método target_Loaded, que é disparado sempre que uma janela é carregada. Isso é feito pelo método HideAllVisible. Na verdade esse método não “esconde” as janelas, pois ao escondê-las, o VisualBrush da mesma também desaparece. Veja que uso o VisualBrush da janela para que posteriormente tenhamos uma lista das janelas abertas sendo exibidas de forma visual mesmo! Como pode ser visto aqui:

Veja lá ao fundo a miniatura das janelas, é exatamente isso que estamos fazendo. Voltando ao esconder janelas, então, para que sua miniatura também não desapareça, apenas movo a posição da janela para uma área não visível e, posteriormente quando for necessário exibí-la novamente, a trago para o centro do monitor:

public void HideAllVisible(int WindowHashCode)
{
    foreach (var view in _windowViewStates)
    {
        if (view.WindowId != WindowHashCode)
        {
            view.Window.Left = -3000;
        }
    }
}

private void CenterWindow(Window w)
 {
     var width = (SystemParameters.PrimaryScreenWidth / 2);
     var tamanhojanela = w.Width / 2;

     var height = SystemParameters.PrimaryScreenHeight / 2;
     var alturajanela = w.Height / 2;
     w.Left = width - tamanhojanela;
     w.Top = height - alturajanela;
 }

Ao fechar uma janela, a removemos da lista e pedimos para que a última que foi aberta, fique ativa novamente:

private static void target_Closed(object sender, EventArgs e)
{
    Window target = (Window) sender;
    if (target != null)
    {
        WindowViewStateManager.Instance.RemoveWindowViewState(target);
        WindowViewStateManager.Instance.ShowLastWindow();
    }
}

public bool RemoveWindowViewState(Window window)
{
    var view = (from v in _windowViewStates where (v.Title == window.Title) && (v.Number.ToString() == window.Tag.ToString()) select v).First();
    if (view != null)
    {
        if (_windowViewStates.Count == 1)
        {
            view.Window.IsEnabled = true;
            view.Window.Owner.Activate();
            view.Window.Owner.Focus();
        }
        _windowViewStates.Remove(view);
        view = null;
        GC.Collect();
        return true;
    }
    return false;
}

public void ShowLastWindow()
{
    if (_windowViewStates.Count > 0)
    {
        var w = _windowViewStates[_windowViewStates.Count - 1];
        CenterWindow(w.Window);
    }

}

Para testar tudo isso foi criado um estilo para um ListBox, onde ele fique na horizontal. Ele irá exibir o conteúdo da lista de janelas gerenciadas. Antes, para definir que uma janela é gerenciada, fazemos uso da AttachedProperty:

<Window x:Class="Test.Window2"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WindowViewState;assembly=WindowViewState"
    Title="Window2" Height="300" Width="300" Background="Aqua" local:WindowViewState.IsManaged="true">
    <Grid>
        <Button Margin="90,109,114,130" Name="button1">Button</Button>
    </Grid>
</Window>

Depois, no construtor da janela principal do projeto de teste, ligamos o datacontext do ListBox à lista de janelas abertas:

public Window1()
       {
           InitializeComponent();
           WindowsList.DataContext = WindowViewStateManager.Instance;
       }

E lá no ListBox utilizo um recurso do Framework Julmar para ligar o evento de SelectionChanged ao comando definido em WindowViewStateManager.

<ListBox x:Name="WindowsList"
                DockPanel.Dock="Top" 
                Style="{DynamicResource WindowsListTemplate}"
                ItemsSource="{Binding Path=WindowViewStates}" 
                ItemContainerStyle="{DynamicResource WindowsListItem}"  
                IsSynchronizedWithCurrentItem="True" 
                SelectedItem="{Binding Path=SelectedWindow}"
                ItemTemplate="{StaticResource WindowsListDataTemplate}" 
                Height="140">
                <julmar:EventCommander.Mappings>
                    <julmar:CommandEvent Command="{Binding CmdSetFocusWindow}" Event="SelectionChanged" />
                </julmar:EventCommander.Mappings>
            </ListBox>

 
public ICommand CmdSetFocusWindow { get; private set; }
public ICommand CmdCloseWindow { get; private set; }

private WindowViewStateManager()
{
    CmdCloseWindow = new DelegatingCommand(CloseWindow, CanCloseWindow);
    CmdSetFocusWindow = new DelegatingCommand(FocusWindow, CanFocusWindow);
}
private void FocusWindow()
{
    if (SelectedWindow !=null)
    SetFocusWindow(SelectedWindow.WindowId);
}

public bool SetFocusWindow(int WindowHashCode)
{
    var view = (from v in _windowViewStates where v.WindowId == WindowHashCode select v).First();
    HideAllVisible(view.WindowId);
    CenterWindow(view.Window);
    return view.Window.Activate();
}

Com isso temos um gerenciador de janelas funcional 🙂  Baixem o código, estudem e comentem !! Sei que aqui não entrei em detalhes profundos, mas qualquer dúvida, é só entrar em contato. Abraço!!

http://cid-b27ccfaa07b93be8.skydrive.live.com/embedrowdetail.aspx/Public/WindowManager.rar

Esse post foi publicado em WPF. Bookmark o link permanente.

3 respostas para Gerenciamento de janelas em WPF

  1. Marcelo disse:

    O Arquivo para Download não está mais disponível, poderia me enviar ?

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s