Динамическая процентная ширина в WPF
может быть, вы, ребята, можете помочь мне понять это: у меня есть словарь и ItemsControl, который привязан к этому словарю. Ключ каждой записи определяет содержимое каждого элемента в ItemsControl, а значение определяет ширину каждого элемента. Большая проблема с этим: ширина-это процентное значение, поэтому он говорит мне, что, например, мой элемент должен быть 20% от его родителя по размеру.
как я могу добиться этого? Я знаю, что сетки могут работать со звездной шириной, но так как я должен определить GridDefinition в начале сетки, я не могу сделать это в элемент управления ItemsControl.Свойства itemtemplate.
текущий код:
<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>
есть идеи по этому поводу? Есть ли элегантный способ решить эту проблему?
спасибо!
пояснения: процент должен быть основан на родительском элементе ItemControls!
и еще один: каждый элемент должен быть одним столбцом сетки, а не строкой. Поэтому мне нужно, чтобы все предметы были рядом друг с другом в одном ряду.
решение:
Спасибо за вашу помощь, эта проблема может быть решена с помощью Multibinding и привязки к ActualWidth ItemsControl. Таким образом, всякий раз, когда ItemsControl изменяется в размере, элементы также меняются. Сетка не нужна. Это решение создает только относительную ширину, но то же самое решение, конечно, может быть применено к высоте элементов. Это короткий версия, для более подробного объяснения см. ниже:
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>
конвертер:
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();
}
}
и это должно сделать трюк!
3 ответов
вы можете реализовать .IValueConverter
обновление.
MultiBinding
поможет вам. Вот пример:
1) xaml:
<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) преобразователь:
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) Модель Вид:
public class ViewModel : ViewModelBase
{
public ViewModel()
{
}
public Double RelativeWidth
{
get { return relativeWidth; }
set
{
if (relativeWidth != value)
{
relativeWidth = value;
OnPropertyChanged("RelativeWidth");
}
}
}
private Double relativeWidth;
}
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 },
};
}
}
MultiBinding
заставляет обновлять цель привязки, когда ActualWidth
изменен.
вы можете попробовать поставить Grid
на ItemTemplate
. Это Grid
будет иметь 2 столбца: 1 для фактического содержимого и один для пустого пространства. Вы должны иметь возможность привязать ширину этих столбцов, используяValue
, возможно, с помощью IValueConverter
.
если содержимое должно быть центрировано, вам нужно создать 3 столбца, разделив пустое пространство между столбцами 0 и 2.
У меня был необходимый, который не мог использовать сетки.
Я создал 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();
}
}