Tôi có thể đặt riêng từng lề trong code nhưng làm cách nào để thực hiện trong XAML, ví dụ: làm thế nào để làm điều này:Cách đặt lề trên chỉ trong XAML?
pseudo-code:
<StackPanel Margin.Top="{Binding TopMargin}">
Tôi có thể đặt riêng từng lề trong code nhưng làm cách nào để thực hiện trong XAML, ví dụ: làm thế nào để làm điều này:Cách đặt lề trên chỉ trong XAML?
pseudo-code:
<StackPanel Margin.Top="{Binding TopMargin}">
Điều quan trọng là nhận ra rằng cài đặt nó trong mã như thế này:
sp2.Margin = new System.Windows.Thickness{ Left = 5 };
tương đương với:
sp2.Margin = new System.Windows.Thickness{ Left = 5, Top = 0, Right = 0, Bottom = 0 };
Bạn không có thể thiết lập chỉ là một giá trị duy nhất trong một phiên bản Thickness
thông qua hoặc mã hoặc XAML. Nếu bạn không đặt một số giá trị, chúng sẽ hoàn toàn bằng không. Do đó, bạn chỉ có thể làm điều này để chuyển đổi các mẫu mã được chấp nhận trong câu hỏi khác của bạn đến một XAML tương đương:
<StackPanel Margin="{Binding TopMargin, Converter={StaticResource MyConverter}}"/>
nơi MyConverter
chỉ trả về một Thickness
mà chỉ đặt Top
và lá tất cả các giá trị khác như bằng không.
Tất nhiên, bạn có thể viết kiểm soát của riêng bạn mà không phơi bày những giá trị cá nhân là tài sản phụ thuộc để làm cho mã của bạn một chút bụi:
<CustomBorder TopMargin="{Binding TopMargin}">
</CustomBorder>
Bạn không thể xác định chỉ lề Top với một ràng buộc, vì Margin
là loại Thickness
mà không phải là một đối tượng phụ thuộc. Tuy nhiên bạn có thể sử dụng một MultiValueConverter
rằng sẽ phải mất 4 giá trị lợi nhuận để làm 1 Độ dày vật
Chuyển đổi:
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>
Làm thế nào công việc này xem xét anh ta muốn đặt một phần duy nhất của lề và để lại các giá trị hiện có khác còn nguyên vẹn? –
Vâng, tất cả các thuộc tính sẽ được đặt lúc khởi tạo, nhưng sau đó bạn chỉ cần thay đổi một trong các thuộc tính bị ràng buộc ... –
BTW, giải pháp của bạn có cùng giới hạn;) –
Sử dụng một bộ chuyển đổi, mã mẫu dưới đây sẽ chuyển đổi đôi bạn đang ràng buộc với độ dày. Nó sẽ thiết lập "Top" của độ dày cho trường bị ràng buộc. Bạn có thể tùy ý sử dụng một ConverterParameter để xác định xem bạn có ràng buộc trái, trên, phải hay dưới cùng.
<StackPanel Margin="{Binding TopMargin, Converter={StaticResource MyThicknessConverter}">
.
public class ThicknessSingleValueConverter : IValueConverter
{
override Convert(...)
{
return new Thickness(0, (double)object, 0, 0);
}
//etc...
Đó không phải là những gì bạn đang tìm kiếm?
<StackPanel Margin="0,10,0,0" />
Giá trị đầu tiên là Lề trái, sau đó Đầu, sau đó Phải và cuối cùng nhưng không kém phần dưới cùng.
Tôi không chắc chắn nếu bạn muốn ràng buộc nó với một cái gì đó, nhưng nếu không, điều đó sẽ làm việc.
Tôi nghĩ Bạn có thể sử dụng cú pháp tài sản, từ MSDN:
<object.Margin>
<Thickness Top="{Binding Top}"/>
</object.Margin>
Thần bạn sẽ không cần bất kỳ chuyển đổi
Nhưng Top không phải là DependancyProperty - trở lại Chuyển đổi
U sẽ nhận được "Không thể đặt thuộc tính chỉ đọc 'System.Windows.Thickness.Top' " – Grigory
Ngoài ra, điều này sẽ vẫn ghi đè lên các giá trị khác với số không mặc định. Vì vậy, nếu đó không phải là hành vi mà bạn muốn, thì điều này sẽ không hoạt động ngay cả với một giá trị mã hóa cứng. – bugged87
Vâng, tôi không nghĩ rằng đó là một giải pháp khả thi. – Gqqnbig
gì sẽ được tốt đẹp là để có thể làm điều này bằng cách xác định một cái gì đó giống như ví dụ mã dưới đây.
<StackPanel Margin=",10,,">
Đáng tiếc là khả năng này dường như không tồn tại theo mặc định trong WPF và đó là một sự xấu hổ bởi vì nó đòi hỏi các nhà phát triển mã được biết đến các giá trị mặc định cứng trong một cách mà sau này làm cho nó khó khăn hơn để da hoặc chủ đề một ứng dụng.
Giải pháp tốt nhất mà tôi có thể nghĩ đến tại thời điểm này là sử dụng trình chuyển đổi, nhưng số lượng mã phụ bạn phải sản xuất để giới thiệu điều này không phải là lý tưởng.
Dưới đây là một giải pháp tiện lợi:
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
}
Nếu bạn thêm mã này vào không gian tên của bạn và xác định các kích thước sau:
<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>
Sau đó bạn có thể tạo Tiny, Small, Medium, Large & lớn lề như sau:
local:Nifty.Margin="H000"
hoặc
local:Nifty.Margin="_S_S"
Sau đó, mã sẽ tạo lề dựa trên tài nguyên của bạn.
này thuộc về những sửa đổi WPF:
Bạn sẽ không thể đặt vị trí của phần tử động bằng cách sử dụng liên kết của thuộc tính hoặc lề được đính kèm mà không cần viết vài dòng mã phía sau.
Đừng so sánh công nghệ này với người khác.
Sự cố của bạn được liệt kê ở # 9.
Có lẽ tôi là "muộn bên", nhưng không thích bất kỳ giải pháp được cung cấp nào, và dường như giải pháp đơn giản nhất và sạch nhất đó là xác định thuộc tính Độ dày trong ViewModel (hoặc bất kỳ thứ gì bạn ràng buộc) và sau đó Liên kết thuộc tính đó. Một cái gì đó như thế này:
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);
}
}
Và sau đó XAML rất đơn giản:
<StackPanel Margin="{Binding Margin}">
Độ dày và lề là các khái niệm liên quan đến xem. Như vậy, chúng không thuộc về một mô hình xem thích hợp. – aaronburro
Đây là một cách đơn giản để làm điều này mà không cần viết bộ chuyển đổi hoặc giá trị biên độ cứng mã hóa. Thứ nhất, xác định như sau trong cửa sổ của bạn (hoặc điều khiển khác) nguồn:
<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>
Lưu ý rằng system
được định nghĩa là xmlns:system="clr-namespace:System;assembly=mscorlib"
.
Bây giờ bạn có thể sử dụng các nguồn lực này như sau:
<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>
Bây giờ nếu bạn muốn thay đổi không gian mặc định giữa điều khiển, bạn chỉ cần thay đổi nó ở một nơi.
Chỉ cần viết một số tài sản gắn liền mà nên làm cho nó dễ dàng để thiết lập một giá trị Margin cá nhân từ một nguồn lực ràng buộc hoặc tĩnh:
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;
}
}
Cách sử dụng:
<TextBlock Text="Test"
app:Margin.Top="{Binding MyValue}"
app:Margin.Right="{StaticResource MyResource}"
app:Margin.Bottom="20" />
Tested trong UWP nhưng điều này nên làm việc cho bất kỳ khung dựa trên XAML nào. Điều tốt đẹp là họ sẽ không ghi đè lên các giá trị khác trên Margin, vì vậy bạn có thể kết hợp chúng.
Tôi sử dụng một ValueConverter liên kết với Margin (RelativeSource Self) và Parse the ConverterParameter, được cho là "top: 123; left: 456".
Trình chuyển đổi chỉ ghi đè các Lề được cung cấp bởi Thông số.
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 sẽ sử dụng Margin do Phong cách trừ margin-top, mà sẽ được ghi đè bằng 0.
Hãy vui vẻ với nó!
Đơn giản, hiệu quả và tôi không chắc tại sao mọi người thử và làm phức tạp điều này. Một dòng là tốt hơn nhiều so với 20 hoặc để một số người nghĩ rằng nó mất. Tôi đánh giá cao câu trả lời này. – Middletone
Bạn có thể đặt nó như thế này 'var margin = sp2.Margin; margin.Left = 5; sp2.Margin = margin; 'Điều này sẽ để nguyên các giá trị khác. – bugged87
@ bugged87: OP muốn làm điều đó trong XAML. –