Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Aprenda a escrever código para uma classe personalizada de Painel
APIs importantes: do Painel, ArrangeOverride,MeasureOverride
O código de exemplo mostra uma implementação de painel personalizado, mas não dedicamos muito tempo explicando os conceitos de layout que influenciam como você pode personalizar um painel para diferentes cenários de layout. Se você quiser mais informações sobre esses conceitos de layout e como eles podem se aplicar ao seu cenário de layout específico, consulte visão geral dos painéis XAML personalizados.
Um painel é um objeto que fornece comportamento de layout para o elemento filho que contém, quando o sistema de layout XAML é executado e a interface do usuário do aplicativo é renderizada. Você pode definir painéis personalizados para layout XAML derivando uma classe personalizada da classe painel . Você fornece comportamento para seu painel sobrescrevendo os métodos ArrangeOverride e MeasureOverride, aplicando lógica que mede e organiza os elementos filho. Este exemplo deriva do Painel. Quando você começa do Panel, os métodos ArrangeOverride e MeasureOverride não têm um comportamento inicial. Seu código está fornecendo o ponto de entrada pelo qual os elementos filho se tornam conhecidos pelo sistema de layout XAML e são renderizados na interface do usuário. Portanto, é muito importante que seu código contemple todos os elementos filho e siga os padrões que o sistema de layout espera.
Seu cenário de layout
Ao definir um painel personalizado, você está definindo um cenário de layout.
Um cenário de layout é expresso por meio de:
- O que o painel fará quando tiver elementos-filho
- Quando o painel tem restrições em seu próprio espaço
- Como a lógica do painel determina todas as medidas, posicionamento, posições e dimensionamentos que eventualmente resultam em um layout de interface do usuário renderizado de filhos
Com isso em mente, o BoxPanel mostrado aqui é para um cenário específico. No interesse de manter o código acima de tudo neste exemplo, ainda não explicaremos o cenário em detalhes e, em vez disso, nos concentraremos nas etapas necessárias e nos padrões de codificação. Se você quiser saber mais sobre o cenário primeiro, vá para "O cenário para BoxPanel" e volte para o código.
Comece derivando do Painel
Comece derivando uma classe personalizada do Painel. Provavelmente, a maneira mais fácil de fazer isso é definir um arquivo de código separado para essa classe, usando as opções de menu de contexto Adicionar | Novo Item | Classe para um projeto do Gerenciador de Soluções no Microsoft Visual Studio. Nomeie a classe (e o arquivo) BoxPanel.
O arquivo de modelo de uma classe não começa com muitas usando instruções porque não é especificamente para aplicativos do Windows. Então, primeiro, adicione usando comandos. Os arquivos de modelo também começam com algumas instruções usando que você provavelmente não precisa e podem ser excluídas. Aqui está uma lista sugerida de usando instruções que podem resolver os tipos necessários para o código de painel personalizado típico:
using System;
using System.Collections.Generic; // if you need to cast IEnumerable for iteration, or define your own collection properties
using Windows.Foundation; // Point, Size, and Rect
using Windows.UI.Xaml; // DependencyObject, UIElement, and FrameworkElement
using Windows.UI.Xaml.Controls; // Panel
using Windows.UI.Xaml.Media; // if you need Brushes or other utilities
Agora que você pode resolver Painel, torne-o a classe base de BoxPanel. Além disso, torne BoxPanel público:
public class BoxPanel : Panel
{
}
No nível da classe, defina alguns int e valores de duplos que serão compartilhados por várias de suas funções lógicas, mas que não precisarão ser expostos como API pública. No exemplo, eles são nomeados: maxrc, , rowcount, colcount, cellwidth, , cellheight, , maxcellheight. aspectratio
Depois de fazer isso, o arquivo de código completo terá esta aparência (removendo comentários sobre como usar, agora que você sabe por que os temos):
using System;
using System.Collections.Generic;
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
public class BoxPanel : Panel
{
int maxrc, rowcount, colcount;
double cellwidth, cellheight, maxcellheight, aspectratio;
}
Daqui em diante, passaremos a mostrar uma definição de membro por vez, seja uma substituição de método ou algo que ofereça suporte, como uma propriedade de dependência. Você pode adicioná-los ao esqueleto acima em qualquer ordem.
MeasureOverride
protected override Size MeasureOverride(Size availableSize)
{
// Determine the square that can contain this number of items.
maxrc = (int)Math.Ceiling(Math.Sqrt(Children.Count));
// Get an aspect ratio from availableSize, decides whether to trim row or column.
aspectratio = availableSize.Width / availableSize.Height;
// Now trim this square down to a rect, many times an entire row or column can be omitted.
if (aspectratio > 1)
{
rowcount = maxrc;
colcount = (maxrc > 2 && Children.Count <= maxrc * (maxrc - 1)) ? maxrc - 1 : maxrc;
}
else
{
rowcount = (maxrc > 2 && Children.Count <= maxrc * (maxrc - 1)) ? maxrc - 1 : maxrc;
colcount = maxrc;
}
// Now that we have a column count, divide available horizontal, that's our cell width.
cellwidth = (int)Math.Floor(availableSize.Width / colcount);
// Next get a cell height, same logic of dividing available vertical by rowcount.
cellheight = Double.IsInfinity(availableSize.Height) ? Double.PositiveInfinity : availableSize.Height / rowcount;
foreach (UIElement child in Children)
{
child.Measure(new Size(cellwidth, cellheight));
maxcellheight = (child.DesiredSize.Height > maxcellheight) ? child.DesiredSize.Height : maxcellheight;
}
return LimitUnboundedSize(availableSize);
}
O padrão necessário de uma implementação MeasureOverride é o loop por cada elemento em Panel.Children. Sempre chame o método medida
A forma como BoxPanel divide seu tamanho é bastante simples: divide seu espaço em várias caixas, que são amplamente controladas pelo número de itens. As caixas são dimensionadas com base na contagem de linhas e colunas e no tamanho disponível. Às vezes, uma linha ou coluna de um quadrado não é necessária, então é descartada e o painel se torna um retângulo ao invés de um quadrado em termos de sua relação linha/coluna. Para obter mais informações sobre como essa lógica foi estabelecida, avance para "O cenário para BoxPanel".
Então, o que a aprovação da medida faz? Ele define um valor para a propriedade somente leitura DesiredSize de cada elemento em que Measure foi chamado. Ter um valor DesiredSize é possivelmente importante quando você chega à fase de organização, pois o DesiredSize comunica o que o tamanho pode ou deve ser ao organizar e durante a renderização final. Mesmo que você não use DesiredSize em sua própria lógica, o sistema ainda precisará dele.
É possível que esse painel seja usado quando o componente de altura do availableSize não for limitado. Se isso for verdade, o painel não tem uma altura conhecida para dividir. Nesse caso, a lógica da aprovação da medida informa a cada filho que ela ainda não tem uma altura limitada. Ele faz isso passando um tamanho
Observação
A lógica interna de StackPanel também tem esse comportamento: StackPanel passa um valor de dimensão infinita para de Medida em crianças, indicando que não há restrição em crianças na dimensão de orientação. StackPanel normalmente se dimensiona dinamicamente, para acomodar todas as crianças em uma pilha que cresce nessa dimensão.
No entanto, o próprio painel não pode retornar um tamanho com um valor infinito de MeasureOverride, que gera uma exceção durante o layout. Portanto, parte da lógica é descobrir a altura máxima que qualquer filho solicita e usar essa altura como a altura da célula, caso isso ainda não venha das restrições de tamanho do próprio painel. Aqui está a função auxiliar LimitUnboundedSize que foi referenciada no código anterior, que então pega essa altura máxima da célula e a utiliza para dar ao painel uma altura finita, devolvendo-a, além de assegurar que cellheight seja um número finito antes que a passagem de arranjo seja iniciada.
// This method limits the panel height when no limit is imposed by the panel's parent.
// That can happen to height if the panel is close to the root of main app window.
// In this case, base the height of a cell on the max height from desired size
// and base the height of the panel on that number times the #rows.
Size LimitUnboundedSize(Size input)
{
if (Double.IsInfinity(input.Height))
{
input.Height = maxcellheight * colcount;
cellheight = maxcellheight;
}
return input;
}
ArrangeOverride
protected override Size ArrangeOverride(Size finalSize)
{
int count = 1;
double x, y;
foreach (UIElement child in Children)
{
x = (count - 1) % colcount * cellwidth;
y = ((int)(count - 1) / colcount) * cellheight;
Point anchorPoint = new Point(x, y);
child.Arrange(new Rect(anchorPoint, child.DesiredSize));
count++;
}
return finalSize;
}
O padrão necessário de uma implementação de ArrangeOverride é percorrer cada elemento em Panel.Children. Sempre chame o método Arrange em cada um desses elementos.
Observe como não há tantos cálculos como no MeasureOverride; isso é típico. O tamanho das crianças já é conhecido a partir da própria lógica MeasureOverride do painel ou do valor DesiredSize de cada criança definido durante a passagem de medição. No entanto, ainda precisamos decidir o local dentro do painel em que cada filho será exibido. Em um painel típico, cada filho deve renderizar em uma posição diferente. Um painel que cria elementos sobrepostos não é desejável para cenários típicos (embora não esteja fora de questão criar painéis que tenham sobreposições propositais, se esse for realmente o cenário pretendido).
Este painel é organizado pelo conceito de linhas e colunas. O número de linhas e colunas já foi calculado (era necessário para medição). Portanto, agora a forma das linhas e colunas mais os tamanhos conhecidos de cada célula contribuem para a lógica de definir uma posição de renderização (o anchorPoint) para cada elemento que este painel contém. Esse Point, juntamente com o Size já conhecido da medida, são usados como os dois componentes que constroem um Rect.
Rect é o tipo de entrada para Arrange.
Às vezes, os painéis precisam recortar seu conteúdo. Se o fizerem, o tamanho recortado será o tamanho presente em DesiredSize, porque a lógica de Medida o define como o mínimo do que foi passado para Medida , ou outros fatores de tamanho natural. Portanto, normalmente, você não precisa verificar especificamente se há recorte durante Organizar; o recorte ocorre apenas com base na passagem do DesiredSize para cada chamada Organize.
Você nem sempre precisa de uma contagem ao percorrer o loop se todas as informações necessárias para definir a posição de renderização forem conhecidas por outros meios. Por exemplo, na lógica de layout Canvas, a posição na coleção Children não importa. Todas as informações necessárias para posicionar cada elemento em um de Tela
É típico que o finalSize de entrada e o tamanho que você retorna de uma implementação de ArrangeOverride sejam os mesmos. Para obter mais informações sobre o motivo, consulte a seção "ArrangeOverride" da visão geral dos painéis personalizados XAML.
Um refinamento: controlando o número de linhas versus colunas
Você pode compilar e usar este painel da mesma forma que está agora. No entanto, adicionaremos mais um refinamento. No código mostrado, a lógica coloca a linha ou coluna extra no lado mais longo na proporção. Mas para um maior controle sobre as formas das células, pode ser desejável escolher um conjunto 4x3 de células em vez de 3x4, mesmo que a proporção do próprio painel seja "retrato". Portanto, adicionaremos uma propriedade de dependência opcional que o consumidor do painel pode definir para controlar esse comportamento. Aqui está a definição da propriedade de dependência, que é muito básica:
// Property
public Orientation Orientation
{
get { return (Orientation)GetValue(OrientationProperty); }
set { SetValue(OrientationProperty, value); }
}
// Dependency Property Registration
public static readonly DependencyProperty OrientationProperty =
DependencyProperty.Register(nameof(Orientation), typeof(Orientation), typeof(BoxPanel), new PropertyMetadata(null, OnOrientationChanged));
// Changed callback so we invalidate our layout when the property changes.
private static void OnOrientationChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
if (dependencyObject is BoxPanel panel)
{
panel.InvalidateMeasure();
}
}
E abaixo está como usar Orientation afeta a lógica de medida em MeasureOverride. Na verdade, tudo o que está sendo feito é mudar a forma como rowcount e colcount são derivados de maxrc e da proporção de aspecto verdadeira, e há diferenças de tamanho correspondentes para cada célula por causa disso. Quando Orientation é Vertical (padrão), ele inverte o valor da verdadeira proporção de aspecto antes de usá-la para calcular a quantidade de linhas e colunas no nosso layout de retângulo em "retrato".
// Get an aspect ratio from availableSize, decides whether to trim row or column.
aspectratio = availableSize.Width / availableSize.Height;
// Transpose aspect ratio based on Orientation property.
if (Orientation == Orientation.Vertical) { aspectratio = 1 / aspectratio; }
O cenário para BoxPanel
O cenário específico para BoxPanel é que é um painel em que um dos principais determinantes de como dividir espaço é conhecer o número de itens filho e dividir o espaço disponível conhecido para o painel. Os painéis são intrinsecamente formatos retangulares. Muitos painéis operam dividindo esse espaço retangular em retângulos adicionais; é o que Grid faz para suas células. No caso do Grid, o tamanho das células é definido por valores ColumnDefinition e RowDefinition e os elementos declaram a célula exata em que entram com Grid.Row e propriedades anexadas grid.column. Obter um bom layout de um Grid geralmente requer saber o número de elementos filho de antemão, de modo que haja células suficientes e cada elemento filho define suas propriedades anexadas para caber em sua própria célula.
Mas e se o número de filhos for dinâmico? Isso é certamente possível; O código do aplicativo pode adicionar itens a coleções, em resposta a qualquer condição dinâmica de tempo de execução que você considere importante o suficiente para que valha a pena atualizar sua interface do usuário. Se você estiver usando associação de dados com coleções/objetos de negócios de suporte, a obtenção de tais atualizações e a atualização da interface do usuário serão tratadas automaticamente, por isso essa é frequentemente a técnica preferida (veja Associação de dados detalhadamente).
Mas nem todos os cenários de aplicativo se prestam à associação de dados. Às vezes, você precisa criar novos elementos de interface do usuário em runtime e torná-los visíveis.
BoxPanel é destinado a este cenário. Uma alteração no número de itens filho não é um problema para BoxPanel porque está usando a contagem de filhos em cálculos e ajusta tanto os elementos filho existentes quanto os novos em um novo layout para que todos caibam.
Um cenário avançado para estender BoxPanel ainda mais (não mostrado aqui) poderia acomodar crianças dinâmicas e usar o desiredSize de uma criança como um fator mais forte para o dimensionamento de células individuais. Esse cenário pode usar tamanhos de linha ou coluna variáveis ou formas que não sejam de grade para que haja menos espaço "desperdiçado". Isso requer uma estratégia de como vários retângulos de diferentes tamanhos e proporções podem se encaixar em um retângulo contido, de modo estético e ocupando o menor espaço possível.
BoxPanel não faz isso; está usando uma técnica mais simples para dividir espaço. A técnica do BoxPanelé determinar o número de mínimos quadrados que seja maior que o número de filhos. Por exemplo, 9 itens caberiam em um quadrado 3x3. 10 itens necessitam de um quadrado 4x4. No entanto, geralmente você pode ajustar itens enquanto ainda remove uma linha ou coluna do quadrado inicial, para economizar espaço. No exemplo em que count=10, isso se encaixa em um retângulo de 4x3 ou 3x4.
Você pode se perguntar por que o painel não escolheria 5x2 para 10 itens, porque isso se encaixa perfeitamente no número do item. No entanto, na prática, os painéis são dimensionados como retângulos que raramente têm uma proporção bem definida. A técnica de quadrados mínimos é uma maneira de influenciar a lógica de dimensionamento para funcionar bem com formas de layout típicas e não incentivar o dimensionamento em que as formas de célula obtêm proporções ímpares.
Tópicos relacionados
Referência
Conceitos
Windows developer