MatrixMan
MatrixMan

Reputation: 1

WPF databinding to object's property

I'm attempting to build a calculator using WPF. I'm using multiple pages so that I can practice using databinding between the pages. However, my display is not binding properly. When I hit "0", I want the display to show "0". The display is not updating when I type in a number.

I realize that there are a number of similar questions on SO. The difference between those questions and mine is that I'm already implementing INotifyPropertyChanged, but that has not solved my issue.

Code Setup

"NumberPad" holds the number buttons 0-9. "Display" is just a large button at the top that is supposed to display the number clicked.

Calculator Screenshot

NumberPad.xaml.cs

public partial class NumberPad : Page
{
    Display m_display;

    public NumberPad()
    {
        InitializeComponent();
        m_display = new Display();
        this.DataContext = m_display;
    }

    private void Button0_Click(object sender, RoutedEventArgs e)
    {
        m_display.EquationText = "0";
    }

    private void Button1_Click(object sender, RoutedEventArgs e)
    {
        m_display.EquationText = "1";
    }
 }

Display.xaml.cs

public partial class Display : Page, INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    private string m_equationText;

    public Display()
    {
        InitializeComponent();
        EquationText = "Hi";
    }

    public string EquationText
    {
        get { return m_equationText; }
        set
        {
            m_equationText = value;
            OnPropertyChanged("EquationText");
        }
    }

    private void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Display.xaml

<Page x:Class="WPFCalculator.Display"
    x:Name="_this"
    ....
    Title="Display">

    <Button Content="{Binding ElementName=_this, Path=EquationText, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource buttonStyle}" FontSize="60">

    </Button>
</Page>

Expected: I should be able to press 0 or 1 and its corresponding number show in the display's button. Actual: only "Hi" is displayed throughout execution.

Important Note about Display.xaml

I added in "ElementName=_this, Path=" to make "Hi" display. If I just had "Binding EquationText", it would not display "Hi" or 0 or 1 or anything. I included this code because it is the only way I can make the Display show anything currently.

Conclusion

All being said, I believe the DataContext is being set to a different object's EquationText property than what is actually being bound. How can I databind the display to the property in such a way that the NumberPad can update it?

Thank you!

Upvotes: 0

Views: 2238

Answers (3)

DRapp
DRapp

Reputation: 48179

Welcome to S/O, an excellent free-source to find and offer help. Although there are plenty of articles on doing MVVM patterns and learning about bindings, you have to have your own mindset sometimes before things "click" to that ahhhhhh moment. Your visual sample is completely fine and known goal. I have created a WPF-Window in similar context. However, I created a single method to accept all the buttons so I did not have to repeatedly create one function for every call. By looking at the sender (button object coming in) and getting it as a "Button" and looking at its Content property, I get the respective 0, 1, 2, and +, -, *, / as needed. For the numbers, I just add to the display, for the signs and equality, I leave that up to you. However, since the value is associated to a public property (via get/set), THAT is what is being "bound to" for the text and you'll see that getting updated.

Now your logic for the math functions to add to your internal "_equationText" property, you will obviously incorporate, clear the display in between functions of +, -, *, / as needed and resume your learning. HTH. I created my window called MyCalculator and you can pretty-much copy/paste and run from there.

MyCalculator.xaml

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="60" />
            <ColumnDefinition Width="60" />
            <ColumnDefinition Width="60" />
            <ColumnDefinition Width="60" />
        </Grid.ColumnDefinitions>

        <Grid.RowDefinitions>
            <RowDefinition Height="60" />
            <RowDefinition Height="60" />
            <RowDefinition Height="60" />
            <RowDefinition Height="60" />
            <RowDefinition Height="60" />
            <RowDefinition Height="60" />
            <RowDefinition Height="60" />
            <RowDefinition Height="60" />
        </Grid.RowDefinitions>


        <TextBox Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="4" 
            IsReadOnly="True"
            Text="{Binding DisplayText}"
            Height="28" FontSize="16"
            TextAlignment="Right"
            VerticalAlignment="Center"/>


        <Button Content="7" Grid.Row="2" Grid.Column="0"
            Click="AnyButton_Click" />

        <Button Content="8" Grid.Row="2" Grid.Column="1" 
            Click="AnyButton_Click" />

        <Button Content="9" Grid.Row="2" Grid.Column="2" 
            Click="AnyButton_Click" />

        <Button Content="+" Grid.Row="2" Grid.Column="3" 
            Click="AnyButton_Click" />


        <Button Content="4" Grid.Row="3" Grid.Column="0"
            Click="AnyButton_Click" />

        <Button Content="5" Grid.Row="3" Grid.Column="1" 
            Click="AnyButton_Click" />

        <Button Content="6" Grid.Row="3" Grid.Column="2" 
            Click="AnyButton_Click" />

        <Button Content="-" Grid.Row="3" Grid.Column="3" 
            Click="AnyButton_Click" />


        <Button Content="1" Grid.Row="4" Grid.Column="0"
            Click="AnyButton_Click" />

        <Button Content="2" Grid.Row="4" Grid.Column="1" 
            Click="AnyButton_Click" />

        <Button Content="3" Grid.Row="4" Grid.Column="2" 
            Click="AnyButton_Click" />

        <Button Content="*" Grid.Row="4" Grid.Column="3" 
            Click="AnyButton_Click" />


        <!--<Button Content="1" Grid.Row="5" Grid.Column="0"
            Click="AnyButton_Click" />-->

        <Button Content="0" Grid.Row="5" Grid.Column="1" 
            Click="AnyButton_Click" />

        <!--<Button Content="3" Grid.Row="5" Grid.Column="2" 
            Click="AnyButton_Click" />-->

        <Button Content="/" Grid.Row="5" Grid.Column="3" 
            Click="AnyButton_Click" />


        <Button Content="=" Grid.Row="6" Grid.Column="3" 
            Click="AnyButton_Click" />
    </Grid>
</Window>

MyCalculator.xaml.cs

using System.ComponentModel;
using System.Windows;

namespace StackHelp
{
    /// <summary>
    /// Interaction logic for MyCalculator.xaml
    /// </summary>
    public partial class MyCalculator : Window, INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public MyCalculator()
        {
            DataContext = this;
            InitializeComponent();
        }

        private void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        private string m_equationText;

        private string _displayText = "Hi";
        public string DisplayText
        { get { return _displayText; }
            set { _displayText = value;
                    OnPropertyChanged( "DisplayText" );
            }
        }


        private void AnyButton_Click(object sender, RoutedEventArgs e)
        {
            var whichBtn = sender as System.Windows.Controls.Button;
            if (whichBtn == null)
                return;

            // pre-clear just because it was sample anyhow.
            if (_displayText == "Hi")
                _displayText = "";

            switch( whichBtn.Content )
            {
                case "0":
                case "1":
                case "2":
                case "3":
                case "4":
                case "5":
                case "6":
                case "7":
                case "8":
                case "9":
                    DisplayText += whichBtn.Content;
                    break;

                case "+":
                case "-":
                case "*":
                case "/":
                case "=":
                    // how to handle...  using your computation learning method
                    // then finish by setting the display text with the new string 
                    // represented answer
                    break;

                default:
                    // invalid button
                    break;
            }

        }
    }
}

Upvotes: 0

Gino Pensuni
Gino Pensuni

Reputation: 366

Let me first try to help you to find a solution to your problem:

It is not necessary to define:

<Button Content="{Binding ElementName = _this, ....}"></Button>

because as you set the new created display instance in your code behind file to the DataContext Property of your actual Window Instance (this.DataContext), it is clear for WPF that the data you want to provide to you window lies in your Display instance set to the DataContext property. When we use DataBinding, WPF searches hierarchically in the element tree for the next element that has a DataContext. This means that since your button itself has no DataContext, WPF goes up a level and looks (since you have no further controls hierarchically above button), whether the DataContext of the Window is set. Since the DataContext in your case was already set in your code behind file, WPF now knows that you want to bind to the data from the DataContext of the window. Well, all you have to do is bind to the property that you want to display. You can do that like this:

<Button Content="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
 AncestorType=Display}, Path=EquationText}"...>

I would recommend you to use MVVM Pattern to get a decent solution of the excercise you are trying to solve (:!

The code you try to write mixes different responsibilities which need to be clearly separated. A window, as shown in the picture above, should represent a view. This means that it would not be necessary for the display box to be a separate page. It would be better using a container, such as a grid, to structure the view. Furthermore there should be no logic in the code behind file to the xaml file. Instead of using button click handles, it would be better to use commands defined in a ViewModel. The ideal state that you want to achieve thanks to MVVM is that you want to completely decompose the view and the model from each other. This makes your code more maintainable and easier to replace. To get back to your click handles: It is not advisable to create a callback for each single number. This makes the code more confusing, especially it introduces redundancy, since in each callback pretty much the same thing happens. You assign a string to the same property. So you would logically have, with 9 keys, 9 callbacks and thus 9x the same code.

My advice to you would be to look at the MVVM Pattern and I'm convinced that your code will be clearer.

Upvotes: 1

Dean Chalk
Dean Chalk

Reputation: 20481

Im not 100% sure whats going on without all of the code, but try this:

<Button Content="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
 AncestorType=Display}, Path=EquationText}" Style="{StaticResource buttonStyle}" FontSize="60">

Upvotes: 0

Related Questions