Я могу устанавливать поля отдельно в код, но как это сделать в XAML, например. как это сделать:
псевдокод:
<StackPanel Margin.Top="{Binding TopMargin}">
Я могу устанавливать поля отдельно в код, но как это сделать в XAML, например. как это сделать:
псевдокод:
<StackPanel Margin.Top="{Binding TopMargin}">
Ключ должен понять, что он устанавливает его в коде следующим образом:
sp2.Margin = new System.Windows.Thickness{ Left = 5 };
эквивалентно:
sp2.Margin = new System.Windows.Thickness{ Left = 5, Top = 0, Right = 0, Bottom = 0 };
Вы не можете установить только одно значение в экземпляре Thickness
через любой код или XAML. Если вы не зададите некоторые из значений, они будут неявно нулевыми. Поэтому вы можете просто сделать это, чтобы преобразовать принятый образец кода в свой другой вопрос в эквивалент XAML:
<StackPanel Margin="{Binding TopMargin, Converter={StaticResource MyConverter}}"/>
где MyConverter
просто возвращает a Thickness
, который устанавливает только Top
и оставляет все остальные значения равными нулю.
Конечно, вы можете написать свой собственный элемент управления, который выставляет эти отдельные значения в качестве свойств зависимостей, чтобы сделать ваш код немного чище:
<CustomBorder TopMargin="{Binding TopMargin}">
</CustomBorder>
Разве это не то, что вы ищете?
<StackPanel Margin="0,10,0,0" />
Первое значение - левое поле, затем верхнее, затем правое и последнее, но не менее. Bottom.
Я не уверен, что вы хотите связать его с чем-то, но если нет, это сработает.
Это относится к поправкам WPF:
Вы не сможете динамически устанавливать позицию элемента, используя привязку либо прикрепленного свойства, либо поля, не записывая несколько строк кода.
Не сравнивайте эту технологию с другими.
Ваша проблема указана в # 9.
Вы не можете определить только верхнее поле с привязкой, потому что Margin
имеет тип Thickness
, который не является объектом зависимостей. Однако вы можете использовать MultiValueConverter
, который будет принимать 4 значения полей, чтобы сделать 1 объект Толщина
Конвертер:
public class ThicknessMultiConverter : IMultiValueConverter
{
#region IMultiValueConverter Members
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
double left = System.Convert.ToDouble(values[0]);
double top = System.Convert.ToDouble(values[1]);
double right = System.Convert.ToDouble(values[2]);
double bottom = System.Convert.ToDouble(values[3]);
return new Thickness(left, top, right, bottom);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
Thickness thickness = (Thickness)value;
return new object[]
{
thickness.Left,
thickness.Top,
thickness.Right,
thickness.Bottom
};
}
#endregion
}
XAML:
<StackPanel>
<StackPanel.Margin>
<MultiBinding Converter="{StaticResource myThicknessConverter}">
<Binding Path="LeftMargin"/>
<Binding Path="TopMargin"/>
<Binding Path="RightMargin"/>
<Binding Path="BottomMargin"/>
</MultiBinding>
</StackPanel.Margin>
</StackPanel>
Используйте конвертер, пример кода ниже преобразует двойной, который вы связываете с толщиной. Он установит "Top" толщины в связанное поле. Вы можете по желанию использовать ConverterParameter, чтобы определить, привязаны ли вы к левому, верхнему, правому или нижнему.
<StackPanel Margin="{Binding TopMargin, Converter={StaticResource MyThicknessConverter}">
.
public class ThicknessSingleValueConverter : IValueConverter
{
override Convert(...)
{
return new Thickness(0, (double)object, 0, 0);
}
//etc...
Здесь отличное решение:
public class Nifty
{
private static double _tiny;
private static double _small;
private static double _medium;
private static double _large;
private static double _huge;
private static bool _resourcesLoaded;
#region Margins
public static readonly DependencyProperty MarginProperty =
DependencyProperty.RegisterAttached("Margin", typeof(string), typeof(Nifty),
new PropertyMetadata(string.Empty,
new PropertyChangedCallback(OnMarginChanged)));
public static Control GetMargin(DependencyObject d)
{
return (Control)d.GetValue(MarginProperty);
}
public static void SetMargin(DependencyObject d, string value)
{
d.SetValue(MarginProperty, value);
}
private static void OnMarginChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
FrameworkElement ctrl = d as FrameworkElement;
if (ctrl == null)
return;
string Margin = (string)d.GetValue(MarginProperty);
ctrl.Margin = ConvertToThickness(Margin);
}
private static Thickness ConvertToThickness(string Margin)
{
var result = new Thickness();
if (!_resourcesLoaded)
{
_tiny = (double)Application.Current.FindResource("TinySpace");
_small = (double)Application.Current.FindResource("SmallSpace");
_medium = (double)Application.Current.FindResource("MediumSpace");
_large = (double)Application.Current.FindResource("LargeSpace");
_huge = (double)Application.Current.FindResource("HugeSpace");
_resourcesLoaded = true;
}
result.Left = CharToThickness(Margin[0]);
result.Top = CharToThickness(Margin[1]);
result.Bottom = CharToThickness(Margin[2]);
result.Right = CharToThickness(Margin[3]);
return result;
}
private static double CharToThickness(char p)
{
switch (p)
{
case 't':
case 'T':
return _tiny;
case 's':
case 'S':
return _small;
case 'm':
case 'M':
return _medium;
case 'l':
case 'L':
return _large;
case 'h':
case 'H':
return _huge;
default:
return 0.0;
}
}
#endregion
}
Если вы добавите этот код в пространство имен и определите следующие размеры:
<system:Double x:Key="TinySpace">2</system:Double>
<system:Double x:Key="SmallSpace">5</system:Double>
<system:Double x:Key="MediumSpace">10</system:Double>
<system:Double x:Key="LargeSpace">20</system:Double>
<system:Double x:Key="HugeSpace">20</system:Double>
Затем вы можете создавать Крошечные, Малые, Средние, Большие и Огромные поля, такие как:
local:Nifty.Margin="H000"
или
local:Nifty.Margin="_S_S"
Затем код будет создавать поля на основе ваших ресурсов.
Может быть, я "опоздал на вечеринку", но не понравился ни одному из предоставленных решений, и мне кажется, что самым простым и чистым решением является определение свойства Thickness в ViewModel (или все, что вы связываете), а затем Bind это свойство. Что-то вроде этого:
public class ItemViewModel
{
public Thickness Margin { get; private set }
public ItemViewModel(ModelClass model)
{
/// You can calculate needed margin here,
/// probably depending on some value from the Model
this.Margin = new Thickness(0,model.TopMargin,0,0);
}
}
И тогда XAML прост:
<StackPanel Margin="{Binding Margin}">
Вот простой способ сделать это, не записывая конвертеры или значения маркера жесткого кодирования. Во-первых, определите следующие ресурсы в вашем окне (или другом контроле):
<Window.Resources>
<!-- Define the default amount of space -->
<system:Double x:Key="Space">10.0</system:Double>
<!-- Border space around a control -->
<Thickness
x:Key="BorderSpace"
Left="{StaticResource Space}"
Top="{StaticResource Space}"
Right="{StaticResource Space}"
Bottom="{StaticResource Space}"
/>
<!-- Space between controls that are positioned vertically -->
<Thickness
x:Key="TopSpace"
Top="{StaticResource Space}"
/>
</Window.Resources>
Обратите внимание, что system
определяется как xmlns:system="clr-namespace:System;assembly=mscorlib"
.
Теперь вы можете использовать эти ресурсы следующим образом:
<Grid
Margin="{StaticResource BorderSpace}"
>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Button
Grid.Row="0"
Content="Button 1"
/>
<Button
Grid.Row="1"
Content="Button 2"
Margin="{StaticResource TopSpace}"
/>
</Grid>
Теперь, если вы хотите изменить пространство по умолчанию между элементами управления, вам нужно только изменить его в одном месте.
Просто написал некоторые прикрепленные свойства, которые должны облегчить установку индивидуального значения Margin из привязки или статического ресурса:
public class Margin
{
public static readonly DependencyProperty LeftProperty = DependencyProperty.RegisterAttached(
"Left",
typeof(double),
typeof(Margin),
new PropertyMetadata(0.0));
public static void SetLeft(UIElement element, double value)
{
var frameworkElement = element as FrameworkElement;
if (frameworkElement != null)
{
Thickness currentMargin = frameworkElement.Margin;
frameworkElement.Margin = new Thickness(value, currentMargin.Top, currentMargin.Right, currentMargin.Bottom);
}
}
public static double GetLeft(UIElement element)
{
return 0;
}
public static readonly DependencyProperty TopProperty = DependencyProperty.RegisterAttached(
"Top",
typeof(double),
typeof(Margin),
new PropertyMetadata(0.0));
public static void SetTop(UIElement element, double value)
{
var frameworkElement = element as FrameworkElement;
if (frameworkElement != null)
{
Thickness currentMargin = frameworkElement.Margin;
frameworkElement.Margin = new Thickness(currentMargin.Left, value, currentMargin.Right, currentMargin.Bottom);
}
}
public static double GetTop(UIElement element)
{
return 0;
}
public static readonly DependencyProperty RightProperty = DependencyProperty.RegisterAttached(
"Right",
typeof(double),
typeof(Margin),
new PropertyMetadata(0.0));
public static void SetRight(UIElement element, double value)
{
var frameworkElement = element as FrameworkElement;
if (frameworkElement != null)
{
Thickness currentMargin = frameworkElement.Margin;
frameworkElement.Margin = new Thickness(currentMargin.Left, currentMargin.Top, value, currentMargin.Bottom);
}
}
public static double GetRight(UIElement element)
{
return 0;
}
public static readonly DependencyProperty BottomProperty = DependencyProperty.RegisterAttached(
"Bottom",
typeof(double),
typeof(Margin),
new PropertyMetadata(0.0));
public static void SetBottom(UIElement element, double value)
{
var frameworkElement = element as FrameworkElement;
if (frameworkElement != null)
{
Thickness currentMargin = frameworkElement.Margin;
frameworkElement.Margin = new Thickness(currentMargin.Left, currentMargin.Top, currentMargin.Right, value);
}
}
public static double GetBottom(UIElement element)
{
return 0;
}
}
Использование:
<TextBlock Text="Test"
app:Margin.Top="{Binding MyValue}"
app:Margin.Right="{StaticResource MyResource}"
app:Margin.Bottom="20" />
Протестировано в UWP, но это должно работать для любой платформы на основе XAML. Приятно, что они не будут переопределять другие значения в Margin, поэтому вы также можете комбинировать их.
Я использую ValueConverter, привязанный к Margin (RelativeSource Self) и Parse the ConverterParameter, указанный как "top: 123; left: 456".
Конвертер только перезаписывает поля, заданные параметром.
public class MarginConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is Thickness)) return new Thickness();
Thickness retMargin = (Thickness) value;
List<string> singleMargins = (parameter as string)?.Split(';').ToList() ?? new List<string>();
singleMargins.ForEach(m => {
switch (m.Split(':').ToList()[0].ToLower().Trim()) {
case "left":
retMargin.Left = double.Parse(m.Split(':').ToList()[1].Trim());
break;
case "top":
retMargin.Top = double.Parse(m.Split(':').ToList()[1].Trim());
break;
case "right":
retMargin.Right = double.Parse(m.Split(':').ToList()[1].Trim());
break;
case "bottom":
retMargin.Bottom = double.Parse(m.Split(':').ToList()[1].Trim());
break;
}
}
);
return retMargin;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
XAML
<TextBlock Margin="{Binding RelativeSource={RelativeSource Self},
Path=Margin,
Converter={StaticResource MarginConverter},
ConverterParameter='top:0'}"
Style="{StaticResource Header}"
Text="My Header" />
TextBlock будет использовать маржу, заданную стилем, кроме Margin-Top, которая будет перезаписана 0.
Получайте удовольствие от этого!
Я думал, что вы можете использовать синтаксис свойств, от MSDN:
<object.Margin>
<Thickness Top="{Binding Top}"/>
</object.Margin>
Чем вам не нужен конвертер
Но Top не DependancyProperty - обратно в конвертер
Что было бы неплохо, так это сделать, указав что-то вроде примера кода ниже.
<StackPanel Margin=",10,,">
К сожалению, эта возможность, по-видимому, не существует по умолчанию в WPF, и это позор, потому что для этого требуется, чтобы разработчики жестко кодировали известные значения по умолчанию таким образом, что впоследствии стало более сложным для кожи или темы приложения.
Лучшее решение, о котором я могу думать на данный момент, это использовать конвертер, но количество дополнительного кода, который вы должны создать, чтобы представить это, не является идеальным.