简体   繁体   中英

How to bind properties both ways WPF

I am a reeeally noob beginner WPF developer, and getting the hang of c#. I am creating an app, where I need a knob Button and a TextBox Display, where the knob adjusts the text from the display, and the display, if text is changed, updates the knob position.

Inage example of my application

I've managed to create the Knob Button, which spins when clicked and dragged, and also managed to bind it's value to the TextBox, it displays the value perfectly, but I can't make the TextBox Text update the Knob's position, which is defined by Angle variable (from the RotateTransform thing), the code is as follows:

<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);
            }
        }
    }
}

The Knob is <VolumeControl/> and Display is <DisplayBPM/> , in the main Window I tried to bind them both:

<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>

The following is the code for 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>

and the 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;
        }

        
    }


}

The problem is that BPM (variable from DisplayBPM, from which TextBox gets its input) doesn't seem to be changed, and if it is changed, it is not changing Angle (variable from RotateTransform that determines the Knob position). Can anyone help me? I know there are probably tons of basic problems, it would really help me if you could explain them to me. Thank you so much!

To start, making a custom user control that has Dependency Properties is not the solution for every problem in WPF .

WPF Apps are primarily architected with MVVM : Model - View - ViewModel

With that stated for your specific need I would keep the VolumeControl as that is the correct way to create custom UserControls that have custom DependencyProperties

I would then delete the DisplayBPM class as it is not needed.

I would setup a ViewModel to interact between your controls that contains a single BPM string property.

Here is an example ViewModel I would use:

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

I would then setup the Window with the VolumeControl and just a TextBox to hold the BPM value. Both of these should have a {Binding BPM, Mode=TwoWay} so that you pass the BPM value between controls.

You can then decide on the TextBox binding if you want the value to take as the user is typing or after the user leaves the field (usually with the Tab key). To have the value update the VolumeControl as the user is typing have UpdateSourceTrigger=PropertyChanged in the binding.

See my example here:

<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>

If you are not familiar with how ViewModels and DataContext work within WPF I would recommend reading up on that as that is the primary way to setup an MVVM architecture for WPF apps.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM