首页 > 解决方案 > 如何以两种方式绑定属性WPF

问题描述

我是一个真正的菜鸟初学者 WPF 开发人员,并且掌握了 c# 的窍门。我正在创建一个应用程序,我需要一个旋钮 Button 和一个 TextBox Display,其中旋钮调整显示屏上的文本,如果文本更改,显示屏会更新旋钮位置。

我的应用程序的 Inage 示例

我设法创建了旋钮按钮,它在单击和拖动时会旋转,并且还设法将它的值绑定到 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);
            }
        }
    }
}

Knob is<VolumeControl/>和 Display is <DisplayBPM/>,在主窗口中我尝试将它们都绑定:

<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 的变量)。谁能帮我?我知道可能有很多基本问题,如果你能向我解释一下,那真的会对我有所帮助。非常感谢!

标签: c#wpfdata-binding

解决方案


首先,制作一个自定义用户控件并Dependency Properties不能解决WPF.

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

根据您的特定需求,我会保留VolumeControl因为这是创建具有自定义的自定义的正确UserControls方法DependencyProperties

然后我会删除该类DisplayBPM,因为它不需要。

我将设置一个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;
}

作为旁注,我建议INotifyPropertyChanged 您继续阅读,因为那里有许多图书馆可以用来帮助 WPFMVVM

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

TextBox然后,如果您希望在用户键入时或在用户离开字段后(通常使用Tab键)获取值,则可以决定绑定。要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>

如果您不熟悉其中的工作方式ViewModelsDataContext工作方式,WPF我建议您阅读一下,因为这是MVVM为应用程序设置架构的主要方式WPF


推荐阅读