Uma classe de painel WPF de dimensionamento automático personalizada

Estou tentando escrever um costumePanel classe para WPF, substituindoMeasureOverride eArrangeOverride mas enquanto éna maioria das vezes trabalhando Estou com um problema estranho que não consigo explicar.

Em particular, depois que eu ligoArrange nos meus itens filhos emArrangeOverride depois de descobrir quais devem ser os tamanhos, eles não estão dimensionando para o tamanho que eu dou a eles e parecem estar dimensionando para o tamanho passado para oMeasure método dentroMeasureOverride.

Estou faltando alguma coisa em como esse sistema deve funcionar? Meu entendimento é que o chamadoMeasure simplesmente faz com que a criança avalie suaDesiredSize com base no availableSize fornecido e não deve afetar seu tamanho final real.

Aqui está o meu código completo (o Painel, btw, destina-se a organizar os filhos da maneira mais eficiente em termos de espaço, dando menos espaço às linhas que não precisam dele e dividindo o espaço restante igualmente entre os demais - atualmente, ele suporta apenas orientação vertical, mas pretendo adicionar a horizontal quando a funcionar corretamente):

Editar: Obrigado pelas respostas. Vou examiná-los mais de perto em um momento. No entanto, deixe-me esclarecer como meu algoritmo pretendido funciona, pois não expliquei isso.

Primeiro, a melhor maneira de pensar no que estou fazendo é imaginar uma grade com cada linha definida como *. Isso divide o espaço uniformemente. No entanto, em alguns casos, o elemento em uma linha pode não precisar de todo esse espaço; se for esse o caso, quero pegar qualquer espaço restante e fornecê-lo às linhas que poderiam usar o espaço. Se nenhuma linha precisar de espaço extra, eu apenas tento espaçar as coisas uniformemente (é isso queextraSpace está fazendo, é apenas para esse caso).

Eu faço isso em duas passagens. O ponto final da primeira passagem é determinar o "tamanho normal" final de uma linha - ou seja, o tamanho das linhas que serão reduzidas (dado um tamanho menor que o tamanho desejado). Eu faço isso percorrendo o item menor para o maior e ajustando o tamanho normal calculado em cada etapa, adicionando o espaço restante de cada item pequeno a cada item maior subsequente até que nenhum item se encaixe e depois quebre.

Na próxima passagem, uso esse valor normal para determinar se um item pode caber ou não, simplesmente usando oMin do tamanho normal com o tamanho desejado do item.

(Também alterei o método anônimo para uma função lambda por simplicidade.)

Edição 2: Meu algoritmo parece funcionar muito bem para determinar o tamanho adequado das crianças. No entanto, as crianças simplesmente não estão aceitando seu tamanho. Eu tentei sugerido GoblinMeasureOverride passando PositiveInfinity e retornando Size (0,0), mas isso faz com que os filhos se desenhem como se não houvesse restrições de espaço. A parte que não é óbvia sobre isso é que está acontecendo por causa de uma chamada paraMeasure. A documentação da Microsoft sobre esse assunto não é clara, já que li várias vezes cada descrição de classe e propriedade. No entanto, agora está claro que chamarMeasure de fato afeta a renderização do filho, então tentarei dividir a lógica entre as duas funções, como sugerido pelo BladeWise.

Resolvido !! Eu consegui funcionar. Como eu suspeitava, eu precisava chamar Measure () duas vezes em cada criança (uma vez para avaliar DesiredSize e um segundo para dar a cada criança sua altura adequada). Parece-me estranho que o layout no WPF seja projetado de uma maneira tão estranha, onde é dividido em duas passagens, mas a passagem Measure realmente faz duas coisas:e dimensiona crianças e o passe de organizar não faz quase nada além de posicionar fisicamente as crianças. Muito bizarro.

Vou postar o código de trabalho na parte inferior.

Primeiro, o código original (quebrado):

protected override Size MeasureOverride( Size availableSize ) {
    foreach ( UIElement child in Children )
        child.Measure( availableSize );

    return availableSize;
}

protected override System.Windows.Size ArrangeOverride( System.Windows.Size finalSize ) {
    double extraSpace = 0.0;
    var sortedChildren = Children.Cast<UIElement>().OrderBy<UIElement, double>( child=>child.DesiredSize.Height; );
    double remainingSpace = finalSize.Height;
    double normalSpace = 0.0;
    int remainingChildren = Children.Count;
    foreach ( UIElement child in sortedChildren ) {
        normalSpace = remainingSpace / remainingChildren;
        if ( child.DesiredSize.Height < normalSpace ) // if == there would be no point continuing as there would be no remaining space
            remainingSpace -= child.DesiredSize.Height;
        else {
            remainingSpace = 0;
            break;
        }
        remainingChildren--;
    }

    // this is only for cases where every child item fits (i.e. the above loop terminates normally):
    extraSpace = remainingSpace / Children.Count;
    double offset = 0.0;

    foreach ( UIElement child in Children ) {
        //child.Measure( new Size( finalSize.Width, normalSpace ) );
        double value = Math.Min( child.DesiredSize.Height, normalSpace ) + extraSpace;
            child.Arrange( new Rect( 0, offset, finalSize.Width, value ) );
        offset += value;
    }

    return finalSize;
}

E aqui está o código de trabalho:

double _normalSpace = 0.0;
double _extraSpace = 0.0;

protected override Size MeasureOverride( Size availableSize ) {
    // first pass to evaluate DesiredSize given available size:
    foreach ( UIElement child in Children )
        child.Measure( availableSize );

    // now determine the "normal" size:
    var sortedChildren = Children.Cast<UIElement>().OrderBy<UIElement, double>( child => child.DesiredSize.Height );
    double remainingSpace = availableSize.Height;
    int remainingChildren = Children.Count;
    foreach ( UIElement child in sortedChildren ) {
        _normalSpace = remainingSpace / remainingChildren;
        if ( child.DesiredSize.Height < _normalSpace ) // if == there would be no point continuing as there would be no remaining space
            remainingSpace -= child.DesiredSize.Height;
        else {
            remainingSpace = 0;
            break;
        }
        remainingChildren--;
    }
    // there will be extra space if every child fits and the above loop terminates normally:
    _extraSpace = remainingSpace / Children.Count; // divide the remaining space up evenly among all children

    // second pass to give each child its proper available size:
    foreach ( UIElement child in Children )
        child.Measure( new Size( availableSize.Width, _normalSpace ) );

    return availableSize;
}

protected override System.Windows.Size ArrangeOverride( System.Windows.Size finalSize ) {
    double offset = 0.0;

    foreach ( UIElement child in Children ) {
        double value = Math.Min( child.DesiredSize.Height, _normalSpace ) + _extraSpace;
        child.Arrange( new Rect( 0, offset, finalSize.Width, value ) );
        offset += value;
    }

    return finalSize;
}

Pode não ser supereficiente o que ter que chamarMeasure duas vezes (e iterandoChildren três vezes), mas funciona. Quaisquer otimizações para o algoritmo serão apreciadas.

questionAnswers(2)

yourAnswerToTheQuestion