Как установить верхнее поле только в XAML?
Я могу установить поля отдельно в коде, но как мне это сделать в XAML, например, как мне это сделать:
Псевдокод:
<StackPanel Margin.Top="{Binding TopMargin}">
12 ответов
Ключ должен понять, что установка этого в коде так:
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
просто возвращает Thickness
который устанавливает только Top
и оставляет все другие значения как ноль.
Конечно, вы можете написать свой собственный элемент управления, который отображает эти отдельные значения как свойства зависимостей, чтобы сделать ваш код немного чище:
<CustomBorder TopMargin="{Binding TopMargin}">
</CustomBorder>
Разве это не то, что вы ищете?
<StackPanel Margin="0,10,0,0" />
Первым значением является Левое поле, затем Верхнее, затем Правое и последнее, но не в последнюю очередь Нижнее.
Я не уверен, если вы хотите связать это с чем-то, но если нет, это сработает.
Это относится к поправкам WPF:
- Я WPF, и вы будете использовать меня при кодировании приложений для Windows - в конце концов.
- Не используйте другие технологии - я не буду кроссплатформенным, но попробую с SL.
- Если вы собираетесь использовать меня - будьте уверены, что знаете, что делаете.
- Каждые 7 дней или часов или минут кодирования я заставляю вас делать перерыв, чтобы перейти на SO.
- Уважайте формы окон.
- MVVM -> INPC, INCC -> вы можете использовать его или использовать с гневом - ваш выбор!
- Не взаимодействуйте с другими приложениями.
- Вы также должны заплатить за смесь.
Вы не сможете установить положение элемента динамически, используя привязку либо прикрепленного свойства, либо поля, не написав несколько строк кода позади.
Не сравнивайте эту технологию с другими.
Ваша проблема указана в #9.
Просто написал несколько прикрепленных свойств, которые должны упростить установку отдельного значения 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
имеет тип 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>
Я думал, что вы можете использовать синтаксис свойства из MSDN:
<object.Margin>
<Thickness Top="{Binding Top}"/>
</object.Margin>
Чем вам не понадобится конвертер
Но Top не DependancyProperty - назад к конвертеру
Вот простой способ сделать это без написания конвертеров или жестких значений полей. Сначала определите в своих ресурсах Window (или других элементах управления) следующее:
<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>
Теперь, если вы хотите изменить пространство по умолчанию между элементами управления, вам нужно изменить его только в одном месте.
Я использую ValueConverter, связанный с Margin (RelativeSource Self), и анализирую параметр 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, заданный стилем, кроме Margin-Top, который будет перезаписан 0.
Веселитесь с этим!
Вот отличное решение:
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>
Затем вы можете создать поля Tiny, Small, Medium, Large & Huge:
local:Nifty.Margin="H000"
или же
local:Nifty.Margin="_S_S"
Код будет создавать поля на основе ваших ресурсов.
Может быть, я "опоздал на вечеринку", но мне не понравилось ни одно из предоставленных решений, и мне кажется, что самое простое и чистое решение - определить свойство Thickness во ViewModel (или что-либо, что вы связываете), а затем связать это свойство. Что-то вроде этого:
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}">
Используйте конвертер, пример кода ниже преобразует двойную привязку к толщине. Это установит "Верх" толщины в связанное поле. При желании вы можете использовать ConverterParameter, чтобы определить, привязываете ли вы слева, сверху, справа или снизу.
<StackPanel Margin="{Binding TopMargin, Converter={StaticResource MyThicknessConverter}">
,
public class ThicknessSingleValueConverter : IValueConverter
{
override Convert(...)
{
return new Thickness(0, (double)object, 0, 0);
}
//etc...
Было бы неплохо иметь возможность сделать это, указав что-то вроде примера кода ниже.
<StackPanel Margin=",10,,">
К сожалению, эта возможность, по-видимому, не существует по умолчанию в WPF, и это позор, потому что она требует, чтобы разработчики жестко закодировали известные значения по умолчанию таким образом, чтобы в дальнейшем это усложняло создание оболочки или темы приложения.
Лучшее решение, которое я могу придумать на данный момент, - это использование конвертера, но объем дополнительного кода, который вам нужно создать, чтобы представить это, не идеален.