如何在 WPF 代码中相对设置按钮位置
How set button position relatively in code behind on WPF
在 WPF 中,我在 canvas 上创建按钮。像这样。
private void DrawBtnTag( List<ValPosCrt> tagPos)
{
int posNum = tagPos.Count;
StackPanel[] temp = new StackPanel[ posNum ];
Button[] btn = new Button[posNum];
for ( int i = 0 ; i < posNum ; i++ )
{
var btntemp = CheckButton(i);
Canvas.SetLeft( btntemp , tagPos [ i ].X );
Canvas.SetTop( btntemp , tagPos [ i ].Y );
cvsMap.Children.Add( btntemp );
btn [ i ] = btntemp;
}
}
private Button CheckButton( int i ) // done
{
var btn = new Button();
btn.Name = "btn" + i.ToString();
btn.Width = 20;
btn.Height = 20;
btn.VerticalAlignment = VerticalAlignment.Stretch;
btn.HorizontalAlignment = HorizontalAlignment.Stretch;
btn.Click += ClickIdx;
return btn;
}
但是当我 运行 此代码并扩展应用程序大小时,按钮位置不会更新。
第二张图片是扩展的应用程序大小,我创建的小按钮是行为锚定对象。
我想让这个按钮跟随背景图像。
Xaml代码仅此
<DockPanel Name="dckPanel" Margin="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Border CornerRadius="6" BorderThickness="2" BorderBrush="RoyalBlue" DockPanel.Dock="Top" >
<Grid>
<Canvas Name="cvsMap" Grid.Column="0" ClipToBounds="True" Margin="15">
<Canvas.Background>
<ImageBrush x:Name="imgMap" Stretch="Uniform" RenderOptions.BitmapScalingMode="Fant" />
</Canvas.Background>
</Canvas>
</Grid>
</Border>
</DockPanel>
我该如何解决这个问题?
** 更新:添加测试代码,修复 Xaml 代码。
这是wpf项目的测试代码
代码隐藏 -
public partial class MainWindow : Window
{
public 主窗口()
{
InitializeComponent();
List<Point> inputdata = new List<Point>();
inputdata.Add( new Point( 250 , 250 ) );
inputdata.Add( new Point( 250 , 300 ) );
inputdata.Add( new Point( 300 , 250 ) );
inputdata.Add( new Point( 250 , 200 ) );
inputdata.Add( new Point( 200 , 250 ) );
DrawBtnTag( inputdata );
imgMap.ImageSource = createImg();
}
private void DrawBtnTag( List<Point> tagPos ) // done
{
int posNum = tagPos.Count;
StackPanel[] temp = new StackPanel[ posNum ];
Button[] btn = new Button[posNum];
for ( int i = 0 ; i < posNum ; i++ )
{
var btntemp = CheckButton(i);
Canvas.SetLeft( btntemp , tagPos [ i ].X );
Canvas.SetTop( btntemp , tagPos [ i ].Y );
cvsMap.Children.Add( btntemp );
btn [ i ] = btntemp;
}
}
private Button CheckButton( int i ) // done
{
var btn = new Button();
btn.Name = "btn" + i.ToString();
btn.Width = 20;
btn.Height = 20;
btn.VerticalAlignment = VerticalAlignment.Stretch;
btn.HorizontalAlignment = HorizontalAlignment.Stretch;
return btn;
}
public BitmapSource createImg()
{
List<System.Windows.Media.Color> colors = new List<System.Windows.Media.Color>();
colors.Add( System.Windows.Media.Colors.Red );
colors.Add( System.Windows.Media.Colors.Blue );
BitmapPalette palette = new BitmapPalette(colors);
System.Windows.Media.PixelFormat pf =
System.Windows.Media.PixelFormats.Indexed1;
int width = 128;
int height = width;
int stride = width/pf.BitsPerPixel;
byte[] pixels = new byte[height*stride];
for ( int i = 0 ; i < height * stride ; ++i )
{
if ( i < height * stride / 2 )
{
pixels [ i ] = 0x00;
}
else
{
pixels [ i ] = 0xff;
}
}
return BitmapSource.Create(
width,
height,
96,
96,
pf,
palette,
pixels,
stride);
}
}
xaml代码-
=> 与上面的 xaml 代码相同。只需将该停靠面板放在 window 之后。像这样 window> .. 这里 ../window>
答案可能有点长,但无需修改即可。
基本上:
- 我为您的 Canvas
添加了一个 SizeChanged 事件
- 它计算背景图片的大小和位置(旧的和新的)
- 对于每个按钮,它计算背景图像的相对位置(旧的和新的)并修改它
- 关于宽高比有很多事情(以确保正确的行为),并且此代码只能使用统一的行为(Stretch="Uniform")
这是修改后的 XAML 代码:
<Canvas Name="cvsMap" Grid.Column="0" ClipToBounds="True" Margin="15" SizeChanged="cvsMap_SizeChanged">
<Canvas.Background>
<ImageBrush x:Name="imgMap" Stretch="Uniform" RenderOptions.BitmapScalingMode="Fant" />
</Canvas.Background>
</Canvas>
然后是后面的代码(copy/paste 在您的 createImg 函数之后):
private void cvsMap_SizeChanged(object sender, SizeChangedEventArgs e)
{
double ratio = imgMap.ImageSource.Width / imgMap.ImageSource.Height;
foreach (Control ctrl in cvsMap.Children)
{
if (ctrl is Button)
{
newBtnPosition((Button)ctrl, ratio, e);
}
}
}
/// <summary>
/// Assign a new position to a ctrl contained into a canvas
/// </summary>
/// <param name="ctrl">control to modify</param>
/// <param name="ratio">ratio of the reference element</param>
/// <param name="e">SizeChanged of the container</param>
private void newBtnPosition(Control ctrl, double ratio, SizeChangedEventArgs e)
{
//Everythong is computed according to the reference element (ImageBrush)
Size oldImgSize, newImgSize;
//Avoid dividing by 0
if (e.PreviousSize.Width * e.PreviousSize.Height * e.NewSize.Width * e.NewSize.Height == 0) { return; }
oldImgSize = RefSize(ratio, e.PreviousSize);
newImgSize = RefSize(ratio, e.NewSize);
Point oldImgPos, newImgPos;
oldImgPos = new Point((e.PreviousSize.Width - oldImgSize.Width) / 2, (e.PreviousSize.Height - oldImgSize.Height) / 2);
newImgPos = new Point((e.NewSize.Width - newImgSize.Width) / 2, (e.NewSize.Height - newImgSize.Height) / 2);
//Retrieve the position of the control according to the ref element
Point ctrlPos = new Point((double)ctrl.GetValue(Canvas.LeftProperty) - oldImgPos.X,
(double)ctrl.GetValue(Canvas.TopProperty) - oldImgPos.Y);
//Compute the new position according to the reference element
ctrlPos.X*=newImgSize.Width / oldImgSize.Width;
ctrlPos.Y *= newImgSize.Height / oldImgSize.Height;
//Assign the new position according to the Canvas
ctrl.SetValue(Canvas.LeftProperty, ctrlPos.X + newImgPos.X);
ctrl.SetValue(Canvas.TopProperty, ctrlPos.Y + newImgPos.Y);
}
/// <summary>
/// Compute a element size, given a aspect ratio, a container size, and a Stretch="Uniform" behavior
/// </summary>
/// <param name="ratio">aspect ratio of the control</param>
/// <param name="containerSize">container size of the control</param>
/// <returns>new size</returns>
private Size RefSize(double ratio, Size containerSize)
{
double cH, cW;
cW = containerSize.Width;
cH = containerSize.Height;
if (cH * cW == 0) { return new Size(0, 0); }
if (cW / cH > ratio)
{
return new Size(cH * ratio, cH);
}
else
{
return new Size(cW, cW/ratio);
}
}
在 WPF 中,我在 canvas 上创建按钮。像这样。
private void DrawBtnTag( List<ValPosCrt> tagPos)
{
int posNum = tagPos.Count;
StackPanel[] temp = new StackPanel[ posNum ];
Button[] btn = new Button[posNum];
for ( int i = 0 ; i < posNum ; i++ )
{
var btntemp = CheckButton(i);
Canvas.SetLeft( btntemp , tagPos [ i ].X );
Canvas.SetTop( btntemp , tagPos [ i ].Y );
cvsMap.Children.Add( btntemp );
btn [ i ] = btntemp;
}
}
private Button CheckButton( int i ) // done
{
var btn = new Button();
btn.Name = "btn" + i.ToString();
btn.Width = 20;
btn.Height = 20;
btn.VerticalAlignment = VerticalAlignment.Stretch;
btn.HorizontalAlignment = HorizontalAlignment.Stretch;
btn.Click += ClickIdx;
return btn;
}
但是当我 运行 此代码并扩展应用程序大小时,按钮位置不会更新。
第二张图片是扩展的应用程序大小,我创建的小按钮是行为锚定对象。 我想让这个按钮跟随背景图像。
Xaml代码仅此
<DockPanel Name="dckPanel" Margin="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Border CornerRadius="6" BorderThickness="2" BorderBrush="RoyalBlue" DockPanel.Dock="Top" >
<Grid>
<Canvas Name="cvsMap" Grid.Column="0" ClipToBounds="True" Margin="15">
<Canvas.Background>
<ImageBrush x:Name="imgMap" Stretch="Uniform" RenderOptions.BitmapScalingMode="Fant" />
</Canvas.Background>
</Canvas>
</Grid>
</Border>
</DockPanel>
我该如何解决这个问题?
** 更新:添加测试代码,修复 Xaml 代码。
这是wpf项目的测试代码
代码隐藏 -
public partial class MainWindow : Window
{ public 主窗口() { InitializeComponent();
List<Point> inputdata = new List<Point>(); inputdata.Add( new Point( 250 , 250 ) ); inputdata.Add( new Point( 250 , 300 ) ); inputdata.Add( new Point( 300 , 250 ) ); inputdata.Add( new Point( 250 , 200 ) ); inputdata.Add( new Point( 200 , 250 ) ); DrawBtnTag( inputdata ); imgMap.ImageSource = createImg(); } private void DrawBtnTag( List<Point> tagPos ) // done { int posNum = tagPos.Count; StackPanel[] temp = new StackPanel[ posNum ]; Button[] btn = new Button[posNum]; for ( int i = 0 ; i < posNum ; i++ ) { var btntemp = CheckButton(i); Canvas.SetLeft( btntemp , tagPos [ i ].X ); Canvas.SetTop( btntemp , tagPos [ i ].Y ); cvsMap.Children.Add( btntemp ); btn [ i ] = btntemp; } } private Button CheckButton( int i ) // done { var btn = new Button(); btn.Name = "btn" + i.ToString(); btn.Width = 20; btn.Height = 20; btn.VerticalAlignment = VerticalAlignment.Stretch; btn.HorizontalAlignment = HorizontalAlignment.Stretch; return btn; } public BitmapSource createImg() { List<System.Windows.Media.Color> colors = new List<System.Windows.Media.Color>(); colors.Add( System.Windows.Media.Colors.Red ); colors.Add( System.Windows.Media.Colors.Blue ); BitmapPalette palette = new BitmapPalette(colors); System.Windows.Media.PixelFormat pf = System.Windows.Media.PixelFormats.Indexed1; int width = 128; int height = width; int stride = width/pf.BitsPerPixel; byte[] pixels = new byte[height*stride]; for ( int i = 0 ; i < height * stride ; ++i ) { if ( i < height * stride / 2 ) { pixels [ i ] = 0x00; } else { pixels [ i ] = 0xff; } } return BitmapSource.Create( width, height, 96, 96, pf, palette, pixels, stride); }
}
xaml代码- => 与上面的 xaml 代码相同。只需将该停靠面板放在 window 之后。像这样 window> .. 这里 ../window>
答案可能有点长,但无需修改即可。
基本上:
- 我为您的 Canvas 添加了一个 SizeChanged 事件
- 它计算背景图片的大小和位置(旧的和新的)
- 对于每个按钮,它计算背景图像的相对位置(旧的和新的)并修改它
- 关于宽高比有很多事情(以确保正确的行为),并且此代码只能使用统一的行为(Stretch="Uniform")
这是修改后的 XAML 代码:
<Canvas Name="cvsMap" Grid.Column="0" ClipToBounds="True" Margin="15" SizeChanged="cvsMap_SizeChanged">
<Canvas.Background>
<ImageBrush x:Name="imgMap" Stretch="Uniform" RenderOptions.BitmapScalingMode="Fant" />
</Canvas.Background>
</Canvas>
然后是后面的代码(copy/paste 在您的 createImg 函数之后):
private void cvsMap_SizeChanged(object sender, SizeChangedEventArgs e)
{
double ratio = imgMap.ImageSource.Width / imgMap.ImageSource.Height;
foreach (Control ctrl in cvsMap.Children)
{
if (ctrl is Button)
{
newBtnPosition((Button)ctrl, ratio, e);
}
}
}
/// <summary>
/// Assign a new position to a ctrl contained into a canvas
/// </summary>
/// <param name="ctrl">control to modify</param>
/// <param name="ratio">ratio of the reference element</param>
/// <param name="e">SizeChanged of the container</param>
private void newBtnPosition(Control ctrl, double ratio, SizeChangedEventArgs e)
{
//Everythong is computed according to the reference element (ImageBrush)
Size oldImgSize, newImgSize;
//Avoid dividing by 0
if (e.PreviousSize.Width * e.PreviousSize.Height * e.NewSize.Width * e.NewSize.Height == 0) { return; }
oldImgSize = RefSize(ratio, e.PreviousSize);
newImgSize = RefSize(ratio, e.NewSize);
Point oldImgPos, newImgPos;
oldImgPos = new Point((e.PreviousSize.Width - oldImgSize.Width) / 2, (e.PreviousSize.Height - oldImgSize.Height) / 2);
newImgPos = new Point((e.NewSize.Width - newImgSize.Width) / 2, (e.NewSize.Height - newImgSize.Height) / 2);
//Retrieve the position of the control according to the ref element
Point ctrlPos = new Point((double)ctrl.GetValue(Canvas.LeftProperty) - oldImgPos.X,
(double)ctrl.GetValue(Canvas.TopProperty) - oldImgPos.Y);
//Compute the new position according to the reference element
ctrlPos.X*=newImgSize.Width / oldImgSize.Width;
ctrlPos.Y *= newImgSize.Height / oldImgSize.Height;
//Assign the new position according to the Canvas
ctrl.SetValue(Canvas.LeftProperty, ctrlPos.X + newImgPos.X);
ctrl.SetValue(Canvas.TopProperty, ctrlPos.Y + newImgPos.Y);
}
/// <summary>
/// Compute a element size, given a aspect ratio, a container size, and a Stretch="Uniform" behavior
/// </summary>
/// <param name="ratio">aspect ratio of the control</param>
/// <param name="containerSize">container size of the control</param>
/// <returns>new size</returns>
private Size RefSize(double ratio, Size containerSize)
{
double cH, cW;
cW = containerSize.Width;
cH = containerSize.Height;
if (cH * cW == 0) { return new Size(0, 0); }
if (cW / cH > ratio)
{
return new Size(cH * ratio, cH);
}
else
{
return new Size(cW, cW/ratio);
}
}