Migrando o MainMenu para MenuStrip e ToolStrip

 

Na versão 2.0 do .NET Framework, o suporte a menus e as barras de ferramentas foram aprimorados. Infelizmente, a estrutura de cada nova classe foi radicalmente alterada e a conversão não é trivial. Além disto, a interface do usuário tem alguns comportamentos inesperados. Este artigo dá algumas dicas de como fazer a conversão da antiga para a nova. Também apresenta extensões simples para o ToolStrip e para as classes MenuStrip, que permitem a personalização do realce do mouseover e a implementação do "Click Through" para melhorar a interface do usuário.

Fundamentos

Provavelmente deveremos ter algum código legado escrito para a versão 1.x do .NET framework, que gostaríamos de atualizar para os menus e barras de ferramentas mais elaborados, disponíveis na versão mais recente. As classes MainMenu e ContextMenu foram substituídas com as classes inteiramente novas: MenuStrip e ContextMenuStrip. Da mesma forma, a classe ToolBar foi substituída com ToolStrip. As novas classes têm uma aparência mais atualizada e podem ser estendidas de novas maneiras. Por exemplo, podemos pôr facilmente ComboBoxes em menus ou barras de progresso em um ToolStrip ou em um StatusStrip. Como as estruturas das novas classes é bastante diferente, a atualização dos antigos controles não é direta. Procuramos em vão na Internet por um processo simples a ser seguido. No final, decidimos verificar o que seria necessário fazer e compartilhar isto com a comunidade Code Project.

 

Tendo enfrentado a dificuldade de fazer a conversão, pudemos descobrir que os novos controles se comportam de um modo inesperado, o qual poderia não ser do nosso agrado. Existem duas questões. A primeira é que, diferentemente da maioria das aplicações Windows, os botões e menus não respondem a um único clique quando o formulário pai não estiver focalizado. Em lugar disto, temos que clicar duas vezes no controle antes do mesmo responder - a primeira vez para ativar o formulário e a segunda vez para ativar o controle. Este é um modelo perfeitamente bem comportado para a interface do usuário e é o modo como o Office 2003 e o Visual Studio 2005 funcionam. Porém, seria melhor se o usuário tivesse a opção de escolher entre este modelo ou a abordagem padrão, na qual um único clique ativa o controle. Além disto, os novos controles têm uma "característica" (estou tentando ser gentil) bastante confusa. Diferentemente do Office 2003, mesmo quando o mouse se mover por cima de um formulário inativo, o ToolStrip ou o MenuStrip ainda exibirão o destaque. Isto nos leva a crer que os controles estão prontos para interagir, porém ainda será requerido um segundo clique.

 

Emprestando o código de Rick Brewster, estendemos as classes ToolStrip e MenuStrip para implementar uma característica que permita um único clique para ativar o controle. Para aqueles que preferem a interface do usuário tipo Office 2003, implementamos também uma característica "Suppress Highlighting", para habilitar o realce do mouseover quando o formulário pai (ou um de seus filhos) for ativado. Foram incluídos o código e uma aplicação de exemplo, para demonstrar o comportamento destas novas classes.

Migrando via Pesquisa e Substituição

Se estivermos escrevendo uma nova aplicação, não teremos nenhum problema em criá-la usando as novas classes MenuStrips e ToolStrips do designer do Visual Studio. Porém, se estivermos migrando código, a conversão de todos os antigos menus e barras de ferramentas utilizando o designer, constituirá uma enorme tarefa. Se usarmos pesquisa e substituição não será tão óbvio como proceder e muitos erros serão facilmente cometidos. A nossa meta será economizar tempo, através de um guia passo por passo para realizar alterações genéricas. Isto não irá abranger todas as possibilidades, portanto, poderemos ter que corrigir algumas coisas após a compilação inicial.

 

Em primeiro lugar, recomendamos veementemente que seja salvo um backup do projeto inteiro antes de começar a edição do mesmo. É bastante arriscado mudarmos manualmente o código criado pelo designer. Se cometermos algum erro ou se as dicas o confundirem, poderá facilmente acabar com código que o designer não saberá interpretar, bloqueando assim a possibilidade de modificar o formulário até que os bugs sejam corrigidos. Em segundo lugar, as novas estruturas de classe são complexas, portanto, as nossas dicas poderão apenas ajudar no início. Nossa sugestão é ir adiante com as alterações simples aqui listadas e a partir dai, se o compilador reclamar, solucionar qualquer problema remanescente.

 

Assumiremos que iremos usar o Visual Studio para editar o código. Abrimos o code view e fechamos o design view para o formulário a ser atualizado. Seguiremos então estas dicas em ordem, usando o texto realçado como string de pesquisa e substituição. Selecionamos as opções "Match Case", "Search Hidden Text" e "Current Document". Fazemos as substituições item a item, verificando se cada uma faz sentido no contexto.

Dicas para Migrar para o MenuStrip e o ContextMenuStrip

·         Substituir MainMenu por MenuStrip

·         Substituir ContextMenu por ContextMenuStrip

·         Se o constructor do MainMenu ou do ContextMenu tiver um argumento, removê-lo

·         Substituir Menu.GetMainMenu() por ToolStripItem.GetCurrentParent() - o valor de retorno é um ToolStrip, para o qual poderemos ter que fazer o typecast explícito para MenuStrip

·         Substituir MenuStrip.MenuItems e ContextMenuStrip.MenuItems por MenuStrip.Items ou ContextMenuStrip.Items – porém, devemos ter certeza de estar fazendo a substituição apenas para MenuStrip ou itens de ContextMenuStrip e não para MenuItems

·         Substituir MenuItem.MenuItems por ToolStripMenuItem.DropDownItems

·         Substituir this.Menu por this.MainMenuStrip

·         Substituir MenuItem por ToolStripMenuItem (e MenuItems por ToolStripMenuItems)

·         Mudar todas as propriedades de ToolStripMenuItem.Index para ToolStripMenuItem.MergeIndex

·         Substituir os eventos do ToolStripMenuItem.Popup pelos eventos do ToolStripDropDownItem.DropDownOpening e eventos do ContextMenuStrip.Popop pelos eventos do ContextMenuItem.Opened.

·         Remover as linhas de configuração das propriedades de MenuItem.Shortcut e restaurar posteriormente as mesmas no designer, ou então, editar a linha que configura a propriedade ToolStripMenuItem.ShortcutKeys para usar a enumeração de Form.Keys

·         Substituir ToolStripMenuItem.ShowShortcut por ToolStripMenuItem.ShowShortcutKeys

·         Remover as linhas que configuram a propriedade ToolStripMenuItem.MergeOrder

·         Remover as linhas que configuram a propriedade ToolStripMenuItem.RadioCheck

·         Remover as linhas que configuram a propriedade ToolStripMenuItem.MdiList

·         Se mnMdiList for o nome do MenuItem que irá exibir a lista de filhos do form MDI, configurá-lo para this.MainMenuStrip.MdiWindowListItem = this.mnMdiList

·         Achar a seção do método InitializeComponent() com as linhas que contêm this.Controls.Add. Se o nome do MenuStrip for mainMenu, acrescentar a seguinte linha ao método: this.Controls.Add(this.mainMenu)

·         Agora compilar o código. Poderão surgir erros. Neste caso, consultar a referência de classe para solucionar qualquer problema remanescente

·         Visualizar o formulário no designer. Se tudo rodar bem, veremos os novos menus. Reajustar o layoute para acomodar o tamanho do novo menu.

·         Configurar qualquer tecla de atalho que não foi editada no passo 11.

·         Todos os separadores aparecerão como itens de menu contendo um único hífen. Clicar neles de direita no designer, selecionando "Convert to" e substituir com um separador real

·         Compilar a aplicação e ver se está rodando!

Dicas para Migrar a ToolStrip

·         Substituir ToolBar por ToolStrip

·         Substituir ToolBarButton por ToolStripButton

·         Substituir ToolBarSepamouser por ToolStripSepamouser

·         Apagar as linhas que configuram as propriedades ToolStrip.ButtonSize, ToolStrip.DropDownArrows e ToolStrip.ShowToolTips - estas propriedades não existem no ToolStrip

·         Substituir o evento manipulador de ToolStrip.ButtonClick por manipuladores separados para cada evento de ToolStripButtonClick

·         Eliminar as linhas que configuram ToolStripButton.Style

·         Substituir ToolStripButton.Pushed por ToolStripButton.Checked

·         Substituir ToolStrip.Buttons.AddRange() por ToolStrip.Items.AddRange()

·         Usar o designer para substituir todas as imagens dos botões. A propriedade ImageList ainda funciona, mas foi descontinuada e não pode mais ser usada no designer

·         Cruzar os dedos, compilar e rodar

Estendendo as classes ToolStrip e MenuStrip

Como mencionamos antes, mudamos o comportamento destes controles para criar uma interface do usuário mais intuitiva. Achamos a maioria das soluções no blog de Rick Brewster em MSDN. O Rick introduz uma propriedade ClickThrough e sobreescreve o método WndProc() para procurar a mensagem WM_MOUSEACTIVATE e mudar o resultado de retorno de "Activate e Eat" para "Activate" quando ClickThrough for verdadeiro. Em um fórum da MSDN, achamos a sugestão de JasonD para suprimir o realçe não desejado quando não está ocorrendo o click through. Ele sugere sobreescrever WndProc() para interceptar a mensagem WM_MOUSEMOVE e desprezá-la a menos que o formulário pai ou um de seus filhos tenham o foco.

 

Combinamos ambas as idéias para criar as classes ToolStripEx e MenuStripEx. O projeto anexo contém o código e um exemplo de uso. Ambas as classes contêm duas novas propriedades que podem ser manipuladas no código ou podem ser configuradas no designer do Visual Studio. Elas são:

 

// Se verdadeiro, ativar o controle com um único clique,

// mesmo que o formulario do controle não tenha o foco

public bool ClickThrough=false;    

 

// Se verdadeiro, não exibir o realce do mouseover

// a menos que o formulario do controle tenha o foco

public bool SuppressHighlighting=false;

 

Por favor, ver os detalhes no projeto de exemplo e rodar a aplicação de exemplo para observar os efeitos. Ao rodá-la, tentar as quatro variações e verificar como os menus e barras de ferramentas respondem quando o formulário principal tiver o foco e quando o formulário menor tiver o foco. O código nas classes ToolStripEx e MenuStripEx é idêntico, apenas define as duas propriedades acima e implementa o método simples WndProc():

 

protected override void WndProc(ref Message m)

{

  // Caso o realce nao for desejado, desprezar os comandos mousemove

  // quando o formulario pai ou um dos seus filhos não possui o foco

  if(m.Msg == WinConst.WM_MOUSEMOVE && this.suppressHighlighting &&

    !this.TopLevelControl.ContainsFocus)

      return;

  else

    base.WndProc(ref m);

 

  // Caso o ClickThrough for desejado, substituir "Activate and Eat" por

  // "Activate" nas mensagens WM_MOUSEACTIVATE

  if(m.Msg == WinConst.WM_MOUSEACTIVATE && this.clickThrough &&

    m.Result == (IntPtr)WinConst.MA_ACTIVATEANDEAT)

      m.Result = (IntPtr)WinConst.MA_ACTIVATE;

}

 

Uma vez construída a aplicação de exemplo, poderemos facilmente incluir as classes estendidas nos nossos próprios projetos, coloque-os no formulário como faria com qualquer classe padrão, e edite-os no designer.