C# 设置相对于主窗口的转换转换 X 和 Y 属性
本文关键字:转换 属性 设置 相对于 窗口 | 更新日期: 2023-09-27 18:30:35
我有一个托管在 ItemsControl 中的 Wrappanel,它被包装在滚动查看器中。环绕面板中的项填充有绑定。每个项目都有一个带有数据触发器的数据模板。该触发器应该将项目动画化到屏幕中心。我试图用 translatetransform 来做到这一点,但问题是 translatetransform 的 X 和 Y 属性是相对于项目本身而不是主机容器的,所以每个项目都有不同的动画。如图所示:
韦伯姆 1韦伯姆 2
项模板 XAML:
<DataTemplate x:Key="CountryItemTemplate">
<Grid
x:Name="gridMain"
Height="Auto"
Width="Auto"
Margin="3"
RenderTransformOrigin="0 0"
Panel.ZIndex="{Binding IsVisible, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}">
<materialDesign:Card
x:Name="cardMain"
Height="350"
Width="310"
RenderTransformOrigin="0.5, 0.5"
materialDesign:ShadowAssist.ShadowDepth="Depth1"
UniformCornerRadius="3">
<Grid Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="50*"/>
<RowDefinition Height="50*"/>
</Grid.RowDefinitions>
<Rectangle
Grid.Row="0">
<Rectangle.Fill>
<ImageBrush ImageSource="{Binding ImageUrl, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
</Rectangle.Fill>
</Rectangle>
<TextBlock
Grid.Row="1"
Foreground="Black"
FontWeight="Regular"
FontSize="25"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Margin="16 24 0 0"
Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<Button
Grid.Row="1"
Style="{DynamicResource MaterialDesignToolButton}"
Content="Edit"
FontSize="15"
Width="85"
Height="35"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Margin="0 0 16 24"
Command="{Binding IsInEditModeToggleCommand}"/>
</Grid>
<materialDesign:Card.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<TranslateTransform/>
</TransformGroup>
</materialDesign:Card.RenderTransform>
</materialDesign:Card>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsInEditMode, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="cardMain"
Storyboard.TargetProperty="(UIElement.RenderTransform).Children[1].(TranslateTransform.X)"
To="300"
By="1"
Duration="0:0:2">
<DoubleAnimation.EasingFunction>
<ExponentialEase
EasingMode="EaseInOut"
Exponent="16"/>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
<DoubleAnimation
Storyboard.TargetName="cardMain"
Storyboard.TargetProperty="(UIElement.RenderTransform).Children[1].(TranslateTransform.Y)"
To="400"
By="1"
Duration="0:0:2">
<DoubleAnimation.EasingFunction>
<ExponentialEase
EasingMode="EaseInOut"
Exponent="16"/>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
我尝试像这样使用UIElement.TranslatePoint:
整个用户控件,其中所有内容都位于:
<UserControl
x:Class="NikolaLukovic.CustomsOfficeApp.Desktop.Views.CountryUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:domain="clr-namespace:NikolaLukovic.CustomsOfficeApp.Desktop.DomainModels"
xmlns:helpers="clr-namespace:NikolaLukovic.CustomsOfficeApp.Desktop.Helpers"
xmlns:mahApps="http://metro.mahapps.com/winfx/xaml/controls"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:validationRules="clr-namespace:NikolaLukovic.CustomsOfficeApp.Desktop.Helpers.ValidationRules"
xmlns:views="clr-namespace:NikolaLukovic.CustomsOfficeApp.Desktop.Views"
xmlns:viewModels="clr-namespace:NikolaLukovic.CustomsOfficeApp.Desktop.ViewModels"
xmlns:local="clr-namespace:NikolaLukovic.CustomsOfficeApp.Desktop.Views"
mc:Ignorable="d"
d:DesignHeight="600"
d:DesignWidth="900">
<UserControl.CacheMode>
<BitmapCache/>
</UserControl.CacheMode>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding LoadDataCommand}" CommandParameter="{Binding ElementName=icMain}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<UserControl.DataContext>
<viewModels:CountryViewModel/>
</UserControl.DataContext>
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../Resources/CountryItemStyle.xaml"/>
<ResourceDictionary Source="../Resources/Icons.xaml"/>
<ResourceDictionary Source="../Resources/MaterialDesignIcons.xaml"/>
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Button.xaml" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Shadows.xaml" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.ToggleButton.xaml" />
</ResourceDictionary.MergedDictionaries>
<BooleanToVisibilityConverter x:Key="BoolToVisConverter"/>
</ResourceDictionary>
</UserControl.Resources>
<materialDesign:DialogHost>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="100"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<ScrollViewer
Grid.Row="0"
Grid.Column="0"
Grid.RowSpan="2"
Grid.ColumnSpan="2"
VerticalScrollBarVisibility="Auto">
<ItemsControl
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
x:Name="icMain"
ItemTemplate="{StaticResource CountryItemTemplate}"
ItemsSource="{Binding Countries, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ScrollViewer>
<Button
Width="45"
Height="45"
Style="{DynamicResource MaterialDesignFloatingActionAccentButton}"
Grid.Column="1"
Grid.Row="1"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Margin="0 10 0 0">
<Viewbox Width="24" Height="24">
<Canvas Width="24" Height="24">
<Path Data="M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z" Fill="White" />
</Canvas>
</Viewbox>
</Button>
</Grid>
</materialDesign:DialogHost>
</UserControl>
数据上下文视图模型:
public class CountryViewModel : BaseViewModel
{
private ObservableCollection<CountryItem> countries;
private DelegateCommand<ItemsControl> loadDataCommand;
public CountryViewModel ( )
{
ViewModelFinder.Add(this);
}
public ObservableCollection<CountryItem> Countries
{
get
{
return countries;
}
set
{
this.countries = value;
this.NotifyPropertyChanged();
}
}
public DelegateCommand<ItemsControl> LoadDataCommand
{
get
{
if ( this.loadDataCommand == null )
this.loadDataCommand = new DelegateCommand<ItemsControl>(async icMain => await LoadDataMethod(icMain));
return this.loadDataCommand;
}
}
private async Task LoadDataMethod (ItemsControl icMain)
{
if ( this.Countries == null )
{
var countries = await CountryService.Instance.GetAllAsync();
this.Countries = new ObservableCollection<CountryItem>();
var util = new Util();
foreach ( var country in countries.Take(40) )
{
var countryItem = new CountryItem
{
Name = country.Name,
Iso2 = country.Iso2,
IsoAlpha3 = country.IsoAlpha3,
IsoUnM49Numerical = country.IsoUnM49Numerical,
Id = country.Id
};
var imageBytes = await util.GetImageBytesAsync(country.CountryFlagUrl);
countryItem.ImageUrl = country.CountryFlagUrl;
this.Countries.Add(countryItem);
countryItem.This = icMain.ItemContainerGenerator.ContainerFromItem(countryItem) as UIElement;
}
}
}
}
表示包装面板中的项的类:
public class CountryItem : ObservableObject
{
private bool isEnabled = true;
private bool isInEditMode;
private bool isVisible = true;
private DelegateCommand isInEditModeToggleCommand;
private string name;
private string iso2;
private string isoAlpha3;
private int isoUnM49Numerical;
private string imageUrl;
private Guid id;
private UIElement @this;
public CountryItem ( )
{
}
public bool IsEnabled
{
get
{
return isEnabled;
}
set
{
this.isEnabled = value;
this.NotifyPropertyChanged();
}
}
[Required(AllowEmptyStrings = false, ErrorMessage = "Name is required.")]
public string Name
{
get
{
return this.name;
}
set
{
this.name = value;
this.NotifyPropertyChanged();
}
}
[Required(AllowEmptyStrings = false, ErrorMessage = "Iso2 is required.")]
public string Iso2
{
get
{
return this.iso2;
}
set
{
this.iso2 = value;
this.NotifyPropertyChanged();
}
}
[Required(AllowEmptyStrings = false, ErrorMessage = "Iso Alpha3 is required.")]
public string IsoAlpha3
{
get
{
return this.isoAlpha3;
}
set
{
this.isoAlpha3 = value;
this.NotifyPropertyChanged();
}
}
[Required(AllowEmptyStrings = false, ErrorMessage = "Iso Un M49 Numerical is required.")]
public int IsoUnM49Numerical
{
get
{
return this.isoUnM49Numerical;
}
set
{
this.isoUnM49Numerical = value;
this.NotifyPropertyChanged();
}
}
public string ImageUrl
{
get
{
return imageUrl;
}
set
{
this.imageUrl = value;
this.NotifyPropertyChanged();
}
}
public Guid Id
{
get
{
return id;
}
set
{
this.id = value;
}
}
public bool IsInEditMode
{
get
{
return isInEditMode;
}
set
{
this.isInEditMode = value;
this.NotifyPropertyChanged();
}
}
public DelegateCommand IsInEditModeToggleCommand
{
get
{
if ( this.isInEditModeToggleCommand == null )
this.isInEditModeToggleCommand = new DelegateCommand(IsInEditModeToggleMethod);
return this.isInEditModeToggleCommand;
}
}
public bool IsVisible
{
get
{
return isVisible;
}
set
{
this.isVisible = value;
this.NotifyPropertyChanged();
}
}
public UIElement This
{
get
{
return this.@this;
}
set
{
this.@this = value;
this.NotifyPropertyChanged();
}
}
private void IsInEditModeToggleMethod ( )
{
var countryViewModel = ViewModelFinder.FindOne<CountryViewModel>();
countryViewModel.Countries.Where(x=>x.Id != this.Id).ToList().ForEach(ci => ci.IsVisible = false);
this.This.TranslatePoint(new Point(400, 500), Application.Current.MainWindow)
this.IsInEditMode = !this.IsInEditMode;
}
}
解决方案是从 xaml 中放弃情节提要动画,并在项对象中以编程方式执行此操作。当我创建这些项以填充环绕面板时,我添加了对已创建 ContentPresenter 的引用,该引用表示项本身和根父级,在本例中为 UserControl。
需要引用的内容演示器来对项目进行动画处理。需要引用的根父用户控件才能获取项在用户控件中的当前位置。
现在动画如下所示:
韦伯姆 3韦伯姆 4
我在国家/地区项中添加了此方法:
private void AnimateToCenter ( )
{
var sbTranslate = new Storyboard();
var daTranslateX = new DoubleAnimation();
var daTranslateY = new DoubleAnimation();
var duration = new Duration(TimeSpan.FromSeconds(2));
daTranslateX.Duration = duration;
daTranslateY.Duration = duration;
sbTranslate.Duration = duration;
var exponentialEase = new ExponentialEase();
exponentialEase.EasingMode = EasingMode.EaseInOut;
exponentialEase.Exponent = 16;
daTranslateX.EasingFunction = exponentialEase;
daTranslateY.EasingFunction = exponentialEase;
sbTranslate.Children.Add(daTranslateX);
sbTranslate.Children.Add(daTranslateY);
Storyboard.SetTarget(daTranslateX, this.This);
Storyboard.SetTarget(daTranslateY, this.This);
this.This.RenderTransform = new TranslateTransform();
Storyboard.SetTargetProperty(daTranslateX, new PropertyPath("(UIElement.RenderTransform).(TranslateTransform.X)"));
Storyboard.SetTargetProperty(daTranslateY, new PropertyPath("(UIElement.RenderTransform).(TranslateTransform.Y)"));
//get the current point of the selected item
var currentPoint = this.This.TranslatePoint(new Point(), this.Parent);
//get actual width and height of the parent so we can calculate to which position to calculate
var parentWidth = (this.Parent as UserControl).ActualWidth;
var parentHeight = (this.Parent as UserControl).ActualHeight;
//get actual width and height of the item itself to center the item correctly
//this is needed because the X and Y coordinates of the item are top and left
var thisWidth = (this.This as ContentPresenter).ActualWidth;
var thisHeight = (this.This as ContentPresenter).ActualHeight;
//animate the item to the center of the screen
daTranslateX.To = (parentWidth / 2) - currentPoint.X - (thisWidth / 2);
daTranslateY.To = (parentHeight / 2) - currentPoint.Y - (thisHeight / 2);
sbTranslate.Begin();
}
它在这里被称为:
private void IsInEditModeToggleMethod ( )
{
this.IsEllipseVisible = true;
var countryViewModel = ViewModelFinder.FindOne<CountryViewModel>();
countryViewModel.Countries.Where(x=>x.Id != this.Id).ToList().ForEach(ci => ci.IsVisible = false);
this.IsInEditMode = !this.IsInEditMode;
this.AnimateToCenter();
}
UIElement.TranslatePoint
函数转换坐标。然后,您只需要一种方法在动画中使用翻译后的值,应该有多种方法可以实现此目的。
一种选择是在集合外部具有专用CurrentEditItem
,并根据是否设置此属性来修改窗口内容模板。这样,窗口和项之间的关系应该更容易建模。但我敢肯定,也可以在您的To
值上抛出绑定和转换器,以便让它们转换窗口到项目的坐标值。
例如,在窗口上实例化为静态资源的转换器,引用窗口和项ConverterParameter
,将允许您转换窗口和项目坐标之间的任何传入值。