Jump to content
  • Revista PROGRAMAR: Já está disponível a edição #60 da revista programar. Faz já o download aqui!

Nazgulled

Como mover um Form apenas na vertical?

Recommended Posts

Nazgulled

Uma das sugestões que me fizeram no tópico do FireNotes foi permitir mover o Form apenas na vertical quando este se encontra "snapped" a um dos lados do ecrã. Ora, eu não faço ideia de como fazer isto... Até faço, mas queria uma solução diferente. A solução que me vem à cabeça seria algo dentro destes moldes:

public partial class Form1 : Form {
  private int _left = 0;
  private int _top = 0;

  public Form1() {
    InitializeComponent();
  }

  private void Form1_LocationChanged(object sender, EventArgs e) {
    if (_left != this.Left) {
      this.Left = _left;
    }
  }

  private void Form1_Load(object sender, EventArgs e) {
    _left = this.Left;
    _top = this.Top;
  }
}

Mas isto é muito mau...

No fundo, o utilizador iria "conseguir" mover o form horizontalmente, mas o código iria repor o valor original. O mal disto? Provoca demasiado "flicker" no movimento. Eu procuro uma solução que permita ao utilizador mover o Form na vertical de forma suave.

O ideal seria ter alguma forma de ignorar o movimento horizontal. Por exemplo, se existir alguma forma de ter acesso a um evento tipo "BeforeLocationChanged", poderíamos verificar a nova posição antes de mover o Form e aqui sim, reponhamos o valor original antes da posição ser alterada e o "flicker" desaparecia. Mas um evento assim, não existe por omissão no Form e não sei se será possível, nem como, criar um.

Ideias?

Share this post


Link to post
Share on other sites
Nazgulled

Depois de muito googlar lá consegui arranjar maneira de o fazer...

Em resumo o que fiz foi brincar com as mensagens do Windows. O grande problema nisto é que a documentação existente para este tipo de coisas não é lá grande espingarda e o que deu trabalho foi encontrar exemplos das mensagens que eu tinha de processar. E para quem quiser saber, tive de processar a mensagem WM_WINDOWPOSCHANGING.

Mostrava um exemplo de código mas ainda não tenho o código limpo para mostrar isto, apenas uma salgalhada de código para ver se conseguia por a funcionar. Assim que limpar o código, posso colocar aqui se estiverem interessados....

Share this post


Link to post
Share on other sites
Caio Proiete

Olá,

Exacto... A mensagem que você precisa tratar é a WM_WINDOWPOSCHANGING. Essa mensagem é recebida ANTES do formulário ser movido, por isso basta você sobrescrever o método WndProc do seu formulário, capturar essa mensagem, e alterar a coordenada X para o valor original do form (Location.X), preservando a posição horizontal do formulário, sempre...

Como você disse que seu código estava uma "salgalhada" hehehe. Fiz um pequeno exemplo, bem simples, do que imagino que seja o seu objectivo:

As informações sobre a movimentação da janela (coordenadas, handle, etc...) estão representadas na seguinte estrutura:

// Essa e a estrutura que vem no LParam
// da mensagem WM_WINDOWPOSCHANGING
[structLayout(LayoutKind.Sequential)]
public struct WindowPos
{
    public IntPtr Hwnd;
    public IntPtr HwndInsertAfter;
    public int X;
    public int Y;
    public int CX;
    public int CY;
    public int Flags;
}

No seu formulário, como disse acima, basta fazer um override no método WndProc, e modificar a mensagem para que o form permaneça sempre na mesma coordenada X:

// Sobrescreve o metodo WndProc, para capturar (e modificar)
// as mensagens do Windows recebidas pelo formulario
protected override void WndProc(ref Message mensagem)
{
    // Identificador da mensagem WM_WINDOWPOSCHANGING
    const int WM_WINDOWPOSCHANGING = 0x46;

    switch (mensagem.Msg)
    {
        case WM_WINDOWPOSCHANGING:
            {
                // Estao a tentar movimentar o formulario...

                // Obtem os dados da estrutura que estao no LParam
                WindowPos newPosition = new WindowPos();
                newPosition = (WindowPos)Marshal.PtrToStructure(mensagem.LParam, typeof(WindowPos));

                // Altera o valor da nova posicao horizontal
                // para o valor original de onde o form foi iniciado
                newPosition.X = this.Location.X;

                // Converte a estrutura modificada de volta para o LParam
                Marshal.StructureToPtr(newPosition, mensagem.LParam, true);
                break;
            }
    }

    // Deixa a classe base tratar a mensagem
    base.WndProc(ref mensagem);
}

Abraços,

Caio Proiete

Share this post


Link to post
Share on other sites
Nazgulled

O meu código está uma salgalhada porque está inserido no código principal da minha aplicação sem qualquer preocupação de sintaxe/indentação, foi só para testar. Porque no fundo, é praticamente igual a esse exemplo.

Só uma nota, esta linha:

WindowPos newPosition = new WindowPos();

é desnecessária. Claro que se for removida, a linha seguinte tem de ser modificada para:

WindowPos newPosition = (WindowPos)Marshal.PtrToStructure(mensagem.LParam, typeof(WindowPos));

E não sei se será boa ideia declarar o WM_WINDOWPOSCHANGING dentro do WndProc. Tanto quanto sei, ele não será declarado de cada vez que o método WndProc for chamado? (o qual é chamado constantemente) De qualquer forma, eu tenho sempre uma classe, NativeMethods para este tipo de coisas. Que segundo o standard do FxCop é o que se deve fazer, claro que isto é subjectivo, mas eu gosto de seguir estes standards.

Share this post


Link to post
Share on other sites
Caio Proiete

Só uma nota, esta linha:

WindowPos newPosition = new WindowPos();

é desnecessária.

Tens toda a razão... É mania de chamar o construtor explicitamente. Venho do C++, e quando sinto falta memset, tento compensar :cheesygrin:

E não sei se será boa ideia declarar o WM_WINDOWPOSCHANGING dentro do WndProc.

É uma boa, desde que esteja declarada como const... Ela só será declarada uma vez, para todas as chamadas.

É equivalente a declará-la como const dentro da classe, excepto que fica no âmbito do método (que é o objectivo, já que não é utilizada em nenhum outro lado).

Abraços,

Caio Proiete

Share this post


Link to post
Share on other sites
Nazgulled

Mas se fica no âmbito do método, quando o método terminar, a variável não será "destruída"? Mesmo sendo uma constante pensei que fosse destruída na mesma e que o const serviria apenas para impedir a alteração do valor... Correcto ou errado?

Share this post


Link to post
Share on other sites
Triton

Mas se fica no âmbito do método, quando o método terminar, a variável não será "destruída"? Mesmo sendo uma constante pensei que fosse destruída na mesma e que o const serviria apenas para impedir a alteração do valor... Correcto ou errado?

Não sei como funciona em .NET, mas se a variável for constante (não pode ser modificada) então o compilador está livre de meter a variável noutra secção de memória (read-only data por exemplo) e assim escusa de criar sempre uma variável em cada chamada da função. Tal como disse isto é apenas um bitaite, não sei como funciona. Se alguém souber, também estou interessado. :)


<3 life

Share this post


Link to post
Share on other sites
Caio Proiete

Boas,

Creio que o Triton disse tudo. O conceito de variáveis const é o mesmo para todas as linguagens... Variáveis do tipo const, são variáveis que não podem ter o seu valor alterado, e ficam em um lugar "especial" da memória. No entanto, o mais comum que se vê por aí, é a declaração de variáveis const no âmbito da classe (normalmente acompanhada de algum modificador como public, protected, private, etc...), e foi por isso que o Ricardo achou estranho.

O .NET oferece a possibilidade de declarar variáveis const dentro de um método, e se encarrega de tratá-la como const na mesma, excepto que apenas o método pode utilizar a variável. Se você fizer dez ou vinte chamadas ao método, apenas uma variávei será criada, na primeira vez que você efectuar a chamada. Se você nunca chegar a executar o método, então nenhuma variável será criada.

Se quiserem ler um pouco mais sobre isso, dêem uma vista de olhos no MSDN, que tem exemplos:

http://msdn.microsoft.com/en-us/library/e6w8fe1b.aspx

Abraços,

Caio Proiete

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×

Important Information

By using this site you accept our Terms of Use and Privacy Policy. We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.