如何以两种方式绑定属性 WPF

How to bind properties both ways WPF

我是一个非常菜鸟的 WPF 开发新手,正在掌握 C# 的窍门。 我正在创建一个应用程序,我需要一个旋钮 Button 和一个 TextBox Display,其中旋钮调整显示屏上的文本,如果文本发生变化,显示屏会更新旋钮位置。

Inage example of my application

我已经成功地创建了旋钮按钮,它在点击和拖动时会旋转,并且还成功地将它的值绑定到 TextBox,它完美地显示了值,但我无法让 TextBox 文本更新旋钮的位置,由Angle变量定义(来自RotateTransform的东西),代码如下:

<UserControl x:Class="quaselaeuespero.VolumeControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:quaselaeuespero"
             mc:Ignorable="d" 
             d:DesignHeight="154" d:DesignWidth="148">
    <Grid>
        <Image Name="aPorradoknob" Source="Knob.png" RenderTransformOrigin="0.5,0.5">
            <Image.RenderTransform>
                <RotateTransform Angle="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:VolumeControl}}, Path=Angle}"/>
            </Image.RenderTransform>
        </Image>
    </Grid>
</UserControl>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace quaselaeuespero
{
    /// <summary>
    /// Interaction logic for VolumeControl.xaml
    /// </summary>
    public partial class VolumeControl : UserControl
    {
        public static readonly DependencyProperty AngleProperty =
        DependencyProperty.Register("Angle", typeof(double), typeof(VolumeControl), new UIPropertyMetadata(0.0));
        public double Angle
        {
            get { return (double)GetValue(AngleProperty); }
            set { SetValue(AngleProperty, value);  }
        }

        public VolumeControl()
        {
            InitializeComponent();
            this.Angle = 120;
            this.MouseLeftButtonDown += new MouseButtonEventHandler(OnMouseLeftButtonDown);
            this.MouseUp += new MouseButtonEventHandler(OnMouseUp);
            this.MouseMove += new MouseEventHandler(OnMouseMove);
        }

        private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            Mouse.Capture(this);
        }

        private void OnMouseUp(object sender, MouseButtonEventArgs e)
        {
            Mouse.Capture(null);
        }

        private void OnMouseMove(object sender, MouseEventArgs e)
        {
            if (Mouse.Captured == this)
            {
                // Get the current mouse position relative to the volume control
                Point currentLocation = Mouse.GetPosition(this);

                // We want to rotate around the center of the knob, not the top corner
                Point knobCenter = new Point(this.ActualHeight / 2, this.ActualWidth / 2);

                // Calculate an angle
                double radians = Math.Atan((currentLocation.Y - knobCenter.Y) /
                                           (currentLocation.X - knobCenter.X));
                this.Angle = radians * 180 / Math.PI;

                // Apply a 180 degree shift when X is negative so that we can rotate
                // all of the way around
                if (currentLocation.X - knobCenter.X < 0)
                {
                    this.Angle += 180;
                }

                if(this.Angle >= -90 && this.Angle <= -45)
                {
                    this.Angle = 270;
                }

                if (this.Angle >= -45 && this.Angle <= 0)
                {
                    this.Angle = 1;
                }
                this.Angle = Math.Round(this.Angle, 1);
            }
        }
    }
}

旋钮是<VolumeControl/>,显示是<DisplayBPM/>,主要Window我试着把它们都绑定:

<Window x:Class="quaselaeuespero.MainWindow"
        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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:quaselaeuespero"
        mc:Ignorable="d"
        Title="MainWindow" Height="540" Width="960">
    <Grid>
        <Grid.Background>
            <ImageBrush ImageSource="Background.png"/>
        </Grid.Background>
        <local:VolumeControl x:Name="Knobão" Margin="123,240,675,111" RenderTransformOrigin="0.5,0.5" Angle="{Binding ElementName=BPMDisplay, Path=BPM, UpdateSourceTrigger=Explicit}"/>
        <local:DisplayBPM x:Name="BPMDisplay" BPM="{Binding ElementName=Knobão, Path=Angle, UpdateSourceTrigger=Explicit}" Margin="68,153,656,274" VerticalAlignment="Center" HorizontalAlignment="Center"/>
    </Grid>


</Window>

DisplayBPM 的代码如下:

<UserControl x:Class="quaselaeuespero.DisplayBPM"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:quaselaeuespero"
             mc:Ignorable="d" 
             d:DesignHeight="79
             " d:DesignWidth="229"
             Name="Display">
    <Grid Margin="0">
        <TextBox x:Name="BPMTexto" Text="{Binding ElementName=Display, Path=BPM}" HorizontalAlignment="Right" Margin="0,0,4,0" Width="222" BorderBrush="{x:Null}" SelectionBrush="{x:Null}" Foreground="#FFCF1D1D" FontSize="80" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" FontFamily="DS-Digital" RenderTransformOrigin="0.5,0.5" CaretBrush="#FFCF1D1D">
            <TextBox.Background>
                <ImageBrush ImageSource="Display BPM.png" Stretch="Uniform"/>
            </TextBox.Background>
        </TextBox>
    </Grid>
</UserControl>

和 DisplayBPM.xaml.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace quaselaeuespero
{
    /// <summary>
    /// Interaction logic for DisplayBPM.xaml
    /// </summary>
    public partial class DisplayBPM : UserControl
    {
        private void BPMTexto_TextChanged(object sender, EventArgs e)
        {
            BPM = Convert.ToDouble(BPMTexto.Text); 
        }
        public static readonly DependencyProperty BPMProperty =
        DependencyProperty.Register("BPM", typeof(double), typeof(DisplayBPM), new UIPropertyMetadata(0.0));
        public double BPM
        {
            get { return (double)GetValue(BPMProperty); }
            set { SetValue(BPMProperty, value); }
        }
        public DisplayBPM()
        {
            InitializeComponent();
            BPM = 1;
        }

        
    }


}

问题是 BPM(来自 DisplayBPM 的变量,TextBox 从中获取输入)似乎没有改变,即使改变了,它也没有改变 Angle(来自决定旋钮位置的 RotateTransform 的变量) ).谁能帮我?我知道可能有很多基本问题,如果你能向我解释一下,那对我真的很有帮助。非常感谢!

首先,创建具有 Dependency Properties 的自定义用户控件并不是解决 WPF 中所有问题的方法。

WPF 应用程序的主要架构是 MVVM:模型 - 视图 - ViewModel

根据您的具体需要,我会 保留 VolumeControl,因为这是创建具有自定义 [=] 的自定义 UserControls 的正确方法18=]

然后我会 删除 DisplayBPM class 因为它不需要。

我会设置一个 ViewModel 以在包含单个 BPM 字符串 属性.

的控件之间进行交互

这是一个示例 ViewModel 我会使用:

public class MainWindowViewModel : INotifyPropertyChanged
{
    private string _bpm;
    public string BPM
    {
        get => _bpm;
        set 
        {
            _bpm = value;
            RaisePropertyChanged(nameof(BPM));
        }
    }

    public void RaisePropertyChanged(string property)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

As a side note, I would suggest reading up on INotifyPropertyChanged as there are many libraries out there that you can use to help with WPF and MVVM

然后我会用 VolumeControl 设置 Window 和一个 TextBox 来保存 BPM 值。这两个都应该有一个 {Binding BPM, Mode=TwoWay} 以便您在控件之间传递 BPM 值。

然后,如果您希望值在用户键入时或在用户离开字段(通常使用 Tab 键)后采用,则可以决定 TextBox 绑定。要在用户键入时更新 VolumeControl 的值,请在绑定中添加 UpdateSourceTrigger=PropertyChanged

在此处查看我的示例:

<Window.DataContext>
    <local:MainWindowViewModel />
</Window.DataContext>
<Grid>
    <Grid.Background>
        <ImageBrush ImageSource="Background.png"/>
    </Grid.Background>
    <local:VolumeControl
        x:Name="Knobão"
        Margin="123,240,675,111"
        RenderTransformOrigin="0.5,0.5"
        Angle="{Binding BPM, Mode=TwoWay}" />
    <TextBox
        Text="{Binding BPM, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
        Margin="68,153,656,274"
        MinWidth="222"
        VerticalAlignment="Center"
        HorizontalAlignment="Center">
        <TextBox.Background>
            <ImageBrush
                ImageSource="Display BPM.png"
                Stretch="Uniform" />
        </TextBox.Background>
    </TextBox>
</Grid>

如果您不熟悉 ViewModelsDataContextWPF 中的工作方式,我建议您阅读该内容,因为这是设置 MVVM WPF 个应用的架构。