Why does not display the content of the label?

Viewed 330

So I have got a view with a label and I have got a ViewModel for it.

ViewModelBase

public abstract class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void RaisePropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    protected virtual bool SetAndRaisePropertyChanged<T>(ref T storage, T value, [CallerMemberName] string propertyName = "")
    {
        if (EqualityComparer<T>.Default.Equals(storage, value))
            return false;
        storage = value;
        this.RaisePropertyChanged(propertyName);
        return true;
    }

    private void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

This is how the ViewModel looks like:

  private string _balance = "1111$";
    public string Balance
    {
        get { return _balance; }
        set { SetAndRaisePropertyChanged(ref _balance, value); }
    }

And here is the view:

    <UserControl x:Class="monei_project.MainUpperView"
         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:project"
         xmlns:vm="clr-namespace:project.ViewModels"
         mc:Ignorable="d" 
         d:DesignHeight="100" d:DesignWidth="2200" FontFamily="Open Sans">
<UserControl.Resources>
    <vm:MainUpperViewModel  x:Key="MainUpperViewModel"/>
</UserControl.Resources>
<Grid DataContext="{Binding Source={StaticResource MainUpperViewModel}}">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="1*" />
        <ColumnDefinition Width="1*" />
        <ColumnDefinition Width="1*" />
        <ColumnDefinition Width="1*" />
        <ColumnDefinition Width="1*" />
        <ColumnDefinition Width="1*" />
        <ColumnDefinition Width="1*" />
        <ColumnDefinition Width="1*" />
        <ColumnDefinition Width="1*" />
        <ColumnDefinition Width="1*" />
        <ColumnDefinition Width="1*" />
        <ColumnDefinition Width="1*" />
        <ColumnDefinition Width="1*" />
        <ColumnDefinition Width="1*" />
        <ColumnDefinition Width="1*" />
        <ColumnDefinition Width="1*" />
        <ColumnDefinition Width="1*" />
        <ColumnDefinition Width="1*" />
        <ColumnDefinition Width="1*" />
        <ColumnDefinition Width="1*" />
        <ColumnDefinition Width="1*" />
        <ColumnDefinition Width="1*" />
    </Grid.ColumnDefinitions>
    <Rectangle Grid.RowSpan="1" Grid.ColumnSpan="22" Fill="#013542"></Rectangle>
    <Label  Grid.Column="6" Grid.ColumnSpan="2" VerticalAlignment="Center" Foreground="White" FontSize="16">Balance:</Label>
    <Label x:Name="lblBalance" Grid.Column="7" Grid.ColumnSpan="5" VerticalAlignment="Center" Foreground="White" FontFamily="Open Sans SemiBold" FontSize="24" Margin="55,28,45,33">
        <Label.Content>
            <Binding Path="Balance"/>
        </Label.Content>
    </Label>
</Grid>

In the designer, I can see the content of the label enter image description here

But when I run the application, the label is empty enter image description here

What is the problem?

I have already created some ViewModel, but there I worked with textboxes. We used INotifyPropertyChanged interface, and I am not sure how does it work, so my guess was, that it sets the content, but won't display it, because the label is not updating, so I tried to use the OnPropertyChanged function with a PropertyChangedEventHandler, what we used earlier to the other ViewModels, but it didn't work either, I do not know what can be wrong.

3 Answers

There are some frameworks providing class already implementing the needed interfaces, if you want to do it yourself, here is a possibility:

First you have your ViewModelBase and all your ViewModels should inherit it

public abstract class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void RaisePropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    protected virtual bool SetAndRaisePropertyChanged<T>(ref T storage, T value, [CallerMemberName] string propertyName = "")
    {
        if (EqualityComparer<T>.Default.Equals(storage, value))
            return false;
        storage = value;
        this.RaisePropertyChanged(propertyName);
        return true;
    }
}

then in your viewmodel you will declare your property like this:

private String _mBalance;
public String Balance
{
  get { return _mBalance; }
  set => SetAndRaisePropertyChanged(ref _mBalance, value);
}

[EDIT]: I want to keep the history of the answer, so check my edit below with full fonctionnal example:

Usually I split in more files, but i wanted to stay simple, so you need 2 files (I'm trying to apply MVVM pattern so i'm adding directories): - Views\MainWindow.xaml - ViewModels\MainWindowViewModel.cs

Views\MainWindow.xaml:

<Window x:Class="StackOverflow_DBG.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:StackOverflow_DBG"
        xmlns:viewmodels="clr-namespace:StackOverflow_DBG.ViewModels"
        mc:Ignorable="d"
        Title="MainWindow" Height="100" Width="400">
    <Window.DataContext>
        <viewmodels:MainWindowViewModel/>
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Label Grid.Row="1" Grid.Column="0" Content="{Binding LabelTxt}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
        <TextBox Grid.Row="1" Grid.Column="1" Text="{Binding ValueTxt}"/>
        <Button Grid.Row="1" Grid.Column="2" Content="Change Label" Command="{Binding ChangeLabel}"/>
    </Grid>
</Window>

ViewModels\MainWindowViewModel.cs:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;

namespace StackOverflow_DBG.ViewModels
{
    public abstract class ViewModelBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void RaisePropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        protected virtual bool SetAndRaisePropertyChanged<T>(ref T storage, T value, [CallerMemberName] string propertyName = "")
        {
            if (EqualityComparer<T>.Default.Equals(storage, value))
                return false;
            storage = value;
            this.RaisePropertyChanged(propertyName);
            return true;
        }
    }

    public class RelayCommand : ICommand
    {
        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }
        private Action methodToExecute;
        private Func<bool> canExecuteEvaluator;
        public RelayCommand(Action methodToExecute, Func<bool> canExecuteEvaluator)
        {
            this.methodToExecute = methodToExecute;
            this.canExecuteEvaluator = canExecuteEvaluator;
        }
        public RelayCommand(Action methodToExecute)
            : this(methodToExecute, null)
        {
        }
        public bool CanExecute(object parameter)
        {
            if (this.canExecuteEvaluator == null)
            {
                return true;
            }
            else
            {
                bool result = this.canExecuteEvaluator.Invoke();
                return result;
            }
        }
        public void Execute(object parameter)
        {
            this.methodToExecute.Invoke();
        }
    }

    class MainWindowViewModel : ViewModelBase
    {
        private String m_LabelTxt = "Foo";
        public String LabelTxt
        {
            get { return m_LabelTxt; }
            set => SetAndRaisePropertyChanged(ref m_LabelTxt, value);
        }

        private String m_ValueTxt;
        public String ValueTxt
        {
            get { return m_ValueTxt; }
            set => SetAndRaisePropertyChanged(ref m_ValueTxt, value);
        }

        private RelayCommand m_ChangeLabel;
        public RelayCommand ChangeLabel
        {
            get { return m_ChangeLabel; }
            set { m_ChangeLabel = value; }
        }

        public MainWindowViewModel()
        {
            ChangeLabel = new RelayCommand(() => {
                if (LabelTxt == "Foo")
                {
                    LabelTxt = "Bar ";
                }
                else
                {
                    LabelTxt = "Foo ";
                }
            });
        }
    }
}

This way you also see how to bind button for example. Press the button to see that the update is well done. If using same directories than me, remember to edit app.xaml to use StartupUri="Views/MainWindow.xaml"> instead of

StartupUri="MainWindow.xaml">

Have you properly set the DataContext on the window/control to your view model? You need to set your DataContext before you are able to use bindings. And as such, you should probably use the proper way for binding:

<Label Content="{Binding Balance}" ... />

Edit:

Okay, I'll give you a concrete example of what I mean. Also, you're going to run into a lot of issues using a view model as a StaticResource. Why do I say this? Because once you start adding dependencies to your view model (accessing business logic, etc), you will need some form of dependency injection (DI) or a similar way to do so.

So you have your ViewModelBase, so I'll use that and not duplicate myself. Here's a simple view model:

public class AccountViewModel : ViewModelBase
{
    string _balance = "1111$";

    public AccountViewModel(string accountNumber)
    {
        AccountNumber = accountNumber;
    }

    public string AccountNumber { get; }

    public string Balance
    {
        get { return _balance; }
        set { SetAndRaisePropertyChanged(ref _balance, value); }
    }
}

Here's the view code (MainWindow.xaml):

<Window x:Class="testProj.MainWindow"
        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">
    <Grid>
        <Label Content="{Binding Balance}" />
    </Grid>
</Window>

And the code behind (MainWindow.xaml.cs):

public partial class MainWindow
{
    public MainWindow(AccountViewModel dataContext)
    {
        DataContext = dataContext;
        InitializeComponent();
    }
}

And for fun, the App.xaml.cs (set up for BuildAction - Page):

public partial class App
{
    [STAThread]
    public static void Main(string[] args)
    {
        new MainWindow(new AccountViewModel("123456789")).ShowDialog();
    }
}

This will show you what you are expecting, and display the Balance correctly. There are a few things you can try to see what your issue is:

  1. Is there any information in the output window when debugging that tells you if a binding error is occurring?

Can you give a shorter version of your application showing all parts (i.e. a short project that duplicates the issue) and upload it somewhere?

Delete the margin solved the problem. I guess the margin pushed out the label.

Related