WPF 中基于百分比的动态宽度

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/11865042/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 04:53:47  来源:igfitidea点击:

Dynamic percentage-based width in WPF

c#wpfdynamicgridwidth

提问by BlackWolf

maybe you guys can help me figure this out: I have a Dictionary and an ItemsControl that is binded to that dictionary. The Key of each entry determines the content of each Item in the ItemsControl and the Value determines the width of each Item. The big problem with this: The width is a percentage value, so it tells me that, for example, my Item needs to be 20% of its parent in size.

也许你们可以帮我解决这个问题:我有一个字典和一个绑定到该字典的 ItemsControl。每个条目的Key决定了ItemsControl中每个Item的内容,Value决定了每个Item的宽度。最大的问题是:宽度是一个百分比值,所以它告诉我,例如,我的 Item 的大小需要是其父项的 20%。

How can I achieve this? I know Grids are able to work with star-based widths, but since I have to define the GridDefinition at the beginning of the Grid I cannot do this in the ItemsControl.ItemTemplate.

我怎样才能做到这一点?我知道网格能够使用基于星的宽度,但是由于我必须在网格的开头定义 GridDefinition,因此我无法在 ItemsControl.ItemTemplate 中执行此操作。

Current code:

当前代码:

<ItemsControl ItemsSource="{Binding Distribution}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> 
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <Grid IsItemsHost="True" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel> 
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <!-- I NEED THIS TO BE A CERTAIN PERCENTAGE IN WIDTH -->
                <Label Content="{Binding Key.Text}" Foreground="{Binding Key.Color}"/> 
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>

Any ideas on this? Is there any elegant way to solve this?

对此有何想法?有没有什么优雅的方法来解决这个问题?

Thanks!

谢谢!

Clarifications: The percentage is supposed to be based on the ItemControls parent!

说明:百分比应该基于 ItemControls 父级!

And another one: Each item is supposed to be one column of the grid, not a row. So I need all the items to be next to each other in the same row.

另一个:每个项目应该是网格的一列,而不是一行。所以我需要所有项目在同一行中彼此相邻。

The solution:

解决方案

Thanks for your help, this problem can be solved by using Multibinding and Binding to the ActualWidth of the ItemsControl. This way, whenever the ItemsControl changes in size, the Items change as well. A Grid is not needed. This solution only creates a relative width, but the same solution can of course be applied to the height of the items. This is a short version, for a more thorough explanation see down below:

感谢您的帮助,这个问题可以通过使用Multibinding和Binding到ItemsControl的ActualWidth来解决。这样,每当 ItemsControl 的大小发生变化时,Items 也会发生变化。不需要网格。此解决方案仅创建相对宽度,但相同的解决方案当然可以应用于项目的高度。这是一个简短的版本,有关更详尽的解释,请参见下文:

XAML:

XAML

<ItemsControl ItemsSource="{Binding Distribution}" Name="itemsControl"  HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
         <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <StackPanel IsItemsHost="True" Orientation="Horizontal" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel> 
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Label Content="{Binding Key.Text}" 
                       Foreground="{Binding Key.Color}">
                    <Label.Width>
                        <MultiBinding Converter="{StaticResource myConverter}">
                            <Binding Path="Value"/>
                            <Binding Path="ActualWidth" ElementName="itemsControl"/>
                        </MultiBinding>
                    </Label.Width>
                </Label>  
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>

Converter:

转换器

class MyConverter : IMultiValueConverter
{
    public object Convert(object[] value, Type targetType, object parameter, CultureInfo culture)
    {
        //[1] contains the ItemsControl.ActualWidth we binded to, [0] the percentage
        //In this case, I assume the percentage is a double between 0 and 1
        return (double)value[1] * (double)value[0];
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

And that should do the trick!

这应该可以解决问题!

回答by Dennis

You can implement IValueConverter.

您可以实施IValueConverter.

UPDATE.

更新

MultiBindingwill help you. Here's the sample:

MultiBinding会帮助你。这是示例:

1) xaml:

1) xml:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication1"
        Title="MainWindow" Height="114" Width="404">
    <Grid>
        <Grid.Resources>
            <local:RelativeWidthConverter x:Key="RelativeWidthConverter"/>
        </Grid.Resources>

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>

        <ItemsControl ItemsSource="{Binding}"
                      x:Name="itemsControl">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Rectangle Fill="Green" Margin="5" Height="20" HorizontalAlignment="Left">
                        <Rectangle.Width>
                            <MultiBinding Converter="{StaticResource RelativeWidthConverter}">
                                <Binding Path="RelativeWidth"/>
                                <Binding Path="ActualWidth" ElementName="itemsControl"/>
                            </MultiBinding>
                        </Rectangle.Width>
                    </Rectangle> 
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </Grid>
</Window>

2) converter:

2)转换器:

public class RelativeWidthConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return ((Double)values[0] * (Double)values[1]) / 100.0;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

3) view model:

3)视图模型:

public class ViewModel : ViewModelBase
{
    public ViewModel()
    {
    }

    public Double RelativeWidth
    {
        get { return relativeWidth; }
        set
        {
            if (relativeWidth != value)
            {
                relativeWidth = value;
                OnPropertyChanged("RelativeWidth");
            }
        }
    }
    private Double relativeWidth;
}

4) code-behind:

4)代码隐藏:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        DataContext = new[] 
        { 
            new ViewModel { RelativeWidth = 20 },
            new ViewModel { RelativeWidth = 40 },
            new ViewModel { RelativeWidth = 60 },
            new ViewModel { RelativeWidth = 100 },
        };
    }
}

MultiBindingforces to update binding target, when ActualWidthchanged.

MultiBindingActualWidth更改时强制更新绑定目标。

回答by Jay

You might try putting a Gridin the ItemTemplate. This Gridwould have 2 columns: 1 for the actual content and one for the empty space. You should be able to bind the width of these columns using your dictionary's Value, possibly with the aid of an IValueConverter.

您可以尝试GridItemTemplate. 这Grid将有 2 列:1 列用于实际内容,一列用于空白区域。您应该能够使用字典的 绑定这些列的宽度Value,可能借助IValueConverter.

If the content needs to be centered, you'd need to create 3 columns, splitting the empty space between columns 0 and 2.

如果内容需要居中,您需要创建 3 列,将第 0 列和第 2 列之间的空白区域分开。

回答by Paul Knopf

I had a required that couldn't use the Grids.

我有一个不能使用网格的要求。

I created a ContentControl that allows me wrap content to add a dynamic percentage width/height.

我创建了一个 ContentControl,它允许我包装内容以添加动态百分比宽度/高度。

/// <summary>
/// This control has a dynamic/percentage width/height
/// </summary>
public class FluentPanel : ContentControl, IValueConverter
{
    #region Dependencie Properties

    public static readonly DependencyProperty WidthPercentageProperty =
        DependencyProperty.Register("WidthPercentage", typeof(int), typeof(FluentPanel), new PropertyMetadata(-1, WidthPercentagePropertyChangedCallback));

    private static void WidthPercentagePropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
    {
        ((FluentPanel)dependencyObject).OnWidthPercentageChange();
    }

    public int WidthPercentage
    {
        get { return (int)GetValue(WidthPercentageProperty); }
        set { SetValue(WidthPercentageProperty, value); }
    }

    public static readonly DependencyProperty HeightPercentageProperty =
        DependencyProperty.Register("HeightPercentage", typeof(int), typeof(FluentPanel), new PropertyMetadata(-1, HeightPercentagePropertyChangedCallback));

    private static void HeightPercentagePropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
    {
        ((FluentPanel)dependencyObject).OnHeightPercentageChanged();
    }

    public int HeightPercentage
    {
        get { return (int)GetValue(HeightPercentageProperty); }
        set { SetValue(HeightPercentageProperty, value); }
    }

    #endregion

    #region Methods

    private void OnWidthPercentageChange()
    {
        if (WidthPercentage == -1)
        {
            ClearValue(WidthProperty);
        }
        else
        {
            SetBinding(WidthProperty, new Binding("ActualWidth") { Source = Parent, Converter = this, ConverterParameter = true });
        }
    }

    private void OnHeightPercentageChanged()
    {
        if (HeightPercentage == -1)
        {
            ClearValue(HeightProperty);
        }
        else
        {
            SetBinding(HeightProperty, new Binding("ActualHeight") { Source = Parent, Converter = this, ConverterParameter = false });
        }
    }

    #endregion

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if ((bool)parameter)
        {
            // width
            return (double)value * (WidthPercentage * .01);
        }
        else
        {
            // height
            return (double)value * (HeightPercentage * .01);
        }
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotSupportedException();
    }
}