-1

EDIT: THIS CODE IS AN UPDATED VERSION USING THE RECOMMENDATIONS BELOW BUT STILL RESULTING IN THE SAME PROBLEM. LINK TO SAMPLE IMAGE OF PROBLEM, [EXAMPLE IMAGE].

This code is meant to update the "AddUserNewUsername" label on a tabitem for creating a new user account for my little program. It use to update the label once the user left the "First Name" and "Last Name" fields. However, after some testing, it looks like the "AddUserNewUsername" string IS being populated, but not updating from blank on the GUI.

I'm really confused. I'm new to binding but I don't really understand how this broke. The only thing I changed was the variable from "NewUsername" to "AddUserNewUsername" and updated everything along with it. Below is a snippet of the relevant code.

MainWindow XAML:

    <Label Name="add_user_uname" Grid.Column="5" Grid.ColumnSpan="6" Grid.Row="7" 
           Content="{Binding Path=AddUserNewUsername, Mode=OneWay}" 
           Style="{DynamicResource add_user_label_uname}"/>

    <TextBox Name="add_user_finame" Grid.Column="5" Grid.ColumnSpan="6" Grid.Row="2" 
             Text="{Binding Path=AddUserFirstName, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}" 
             Style="{DynamicResource add_user_text_box_std}"/>

    <TextBox Name="add_user_lname" Grid.Column="5" Grid.ColumnSpan="6" Grid.Row="3" 
             Text="{Binding Path=AddUserLastName, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}" 
             Style="{DynamicResource add_user_text_box_std}"/>

MainWindow Code Behind:

    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = id;
    }

    public Instance_Data id = new Instance_Data();

User_Management Class (Just to show where the message box comes from.):

public static void AttemptUserCreation(MainWindow parent, string finame, string lname, string email, string pword, string verify_pword, string uname, string current_user)
    {
        MessageBox.Show(parent.id.AddUserNewUsername);
        return;

Instance_Data Class:

public class Instance_Data : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    private string _addUserFirstName;
    private string _addUserLastName;

    public string AddUserFirstName
    {
        get { return _addUserFirstName; }
        set
        {
            _addUserFirstName = value;
            OnPropertyChanged("AddUserFirstName");
            OnPropertyChanged("AddUserNewUsername");
        }

    }
    public string AddUserLastName
    {
        get { return _addUserLastName; }
        set
        {
            _addUserLastName = value;
            OnPropertyChanged("AddUserLastName");
            OnPropertyChanged("AddUserNewUsername");
        }
    }
    public string AddUserNewUsername
    {
        get
        {
            if (!String.IsNullOrEmpty(AddUserFirstName))
            {
                return AddUserFirstName[0] + AddUserLastName;
            }
            else
            {
                return AddUserFirstName;
            }
        }
    }

add_user_label_uname Style as requested:

    <Style x:Key="add_user_label_uname" TargetType="{x:Type Label}">
        <Setter Property="HorizontalAlignment" Value="Left"/>
        <Setter Property="FontFamily" Value="Tahoma"/>
        <Setter Property="FontSize" Value="10"/>
        <Setter Property="Foreground" Value="#B45A00"/>
    </Style>

I spent the end of my work day yesterday trying to figure this out and cannot seem to find my hiccup. Hopefully I provided enough data but should you need more just ask! Thank you!

  • Where are you setting `AddUserLastName` and/or `AddUserFirstName`? – 15ee8f99-57ff-4f92-890c-b56153 Sep 14 '17 at 14:38
  • Excellent question, sorry about that! I've updated the post to show where those two strings are populated. – Jonny Small Sep 14 '17 at 14:42
  • 1
    The bindings are set to default update mode which is `LostFocus` meaning they will only write back once you leave the `TextBox`. If you set `UpdateSourceTrigger=PropertyChanged` then they will update on typing. Also if `AddUserFirstName` is `null` the getter of `AddUserNewName` will cause an exception. – Adwaenyth Sep 14 '17 at 14:44
  • Hmm... Okay that makes sense. Thank you for the pointer for the real time updating! Do you think it's not updating due to some sort of exception with the code under the "AddUserNewUsername" GET code? – Jonny Small Sep 14 '17 at 14:46
  • 1
    @JonnySmall One bug is this: `if (AddUserFirstName != "") { return AddUserFirstName[0] + AddUserLastName;` -- if `AddUserFirstName` is `null`, it isn't an empty string, but you get a null reference exception if you then try to index it. You should check `if (!String.IsNullOrEmpty(AddUserFirstName))...` – 15ee8f99-57ff-4f92-890c-b56153 Sep 14 '17 at 14:48
  • In the Getter don't use `AddUserFirstName != ""` but instead at least `!string.IsNullOrEmpty(AddUserFirstName)` as condition. – Adwaenyth Sep 14 '17 at 14:48
  • ...so just do what Adwaenyth says, but also remove `, Mode=OneWay` from the Content binding on the label. Makes no sense; a binding on `Label.Content` is one way by default, and can't be anything else anyway because Label is a read-only control. – 15ee8f99-57ff-4f92-890c-b56153 Sep 14 '17 at 14:53

2 Answers2

3

After much probing, we discovered that a seemingly innocuous line in your C# code was clearing the Binding on your Label:

parent.add_user_uname.Content = "";

When you set a dependency property using its set accessor, you set a local value. That value will clear any OneWay bindings that had been applied to the same property.

As a rule, if you are going to be binding against some sort of view model or data model, you should stick to manipulating the model rather than the view that binds to it. Following that approach would have saved you hours of frustrated debugging. On the upside, you won't make that mistake again :).

Dependency properties have many subtleties. Not only will setting a local value fully replace the previous value (including a one-way, source-to-target binding), but it will override values being supplied by setters and triggers too. These subtleties invariably cause some confusion for new WPF developers. If you want to succeed at WPF development, you should dive into the detailed MSDN documentation. I also recommend Adam Nathan's excellent book WPF 4.5 Unleashed. Chapter 3, the most relevant to this subject, used to be available for free online, and it still might be.

As @Iqon points out, you also had a potential NullReferenceException in your AddUserNewUsername accessor

Mike Strobel
  • 25,075
  • 57
  • 69
  • 1
    "That value will clear any bindings" should actually read "... will clear any OneWay bindings". A TwoWay binding would remain in place, and its source property would be set. – Clemens Sep 14 '17 at 15:59
  • 1
    @Clemens Good catch. Hadn't thought about that in years, which goes to show that if you follow MVVM or similar practices, you hardly ever have to think about these things :). – Mike Strobel Sep 14 '17 at 16:01
0

The only possible way of breaking I see is in the getter of AddUserNewUsername. If AddUserFirstName is null, you will get a NullReferenceException, because you only check if the string is empty.

Change the getter to:

public string AddUserNewUsername
{
    get
    {
        if (!String.IsNullOrEmpty(AddUserFirstName))
        {
            return AddUserFirstName[0] + AddUserLastName;
        }
        else
        {
            return AddUserFirstName;
        }
    }
}
Iqon
  • 1,920
  • 12
  • 20
  • `string.Empty` and `""` are equivalent. See [this question](https://stackoverflow.com/questions/2905378/string-empty-versus) for reference. It's just about what is easier to read. – Adwaenyth Sep 14 '17 at 14:52
  • not exactly, but the difference is mostly in readability. https://stackoverflow.com/questions/151472/what-is-the-difference-between-string-empty-and-empty-string – Iqon Sep 14 '17 at 14:56
  • I've updated the code as you, @Adwaenyth and Ed have stated. However, it's ending with the same results. Here's a link to the example. [Example Image](https://imgur.com/a/3GRgY) – Jonny Small Sep 14 '17 at 14:57
  • 2
    Since this is WPF and as such clearly post .NET 2.0, there is no object created for "" and it both is treated identically. – Adwaenyth Sep 14 '17 at 14:58
  • @JonnySmall I'm testing your code and the label updates as expected, *if* I tab out of each text box after typing a new value. Also btw `""` > `String.Empty` IMO; it's more visually distinctive and uses less characters too. – 15ee8f99-57ff-4f92-890c-b56153 Sep 14 '17 at 14:59
  • @JonnySmall Note that if you don't want to rely on a focus change to commit the first and last names, you can specify `UpdateSourceTrigger=PropertyChanged` on each `Binding`. Then your username label will update as you type. Also, some workplaces block most image hosting sites; if you attach the image to your question, it will be hosted on StackOverflow, and it will be more accessible. – Mike Strobel Sep 14 '17 at 15:00
  • @JonnySmall Where's the code that pops up that messagebox? – 15ee8f99-57ff-4f92-890c-b56153 Sep 14 '17 at 15:01
  • @EdPlunkett I've updated my code to update on property change now. I don't really know what else to do. I've posted an image of my results of the updated code. The string itself is updating, however the label itself still shows as blank. I'll just have to keep scrubbing I guess... – Jonny Small Sep 14 '17 at 15:02
  • 1
    @JonnySmall Let's see the code for your `add_user_label_uname` style. – Mike Strobel Sep 14 '17 at 15:03
  • 1
    @EdPlunkett Located in a User_Management class which handles the creation, editing, and deletion of the users. The user creation method is really long but I'll add the top of it where the message box is created to the post. – Jonny Small Sep 14 '17 at 15:04
  • @MikeStrobel I've updated the post and added the style to the bottom. I've also hosted the image via StackOverflow. Thanks for the pointer. – Jonny Small Sep 14 '17 at 15:08
  • 1
    @JonnySmall Let's step back for a moment and verify that you can see your label _at all_. Remove the `Binding` and set some explicit `Content`. Does that content show up where you expect? Also, you added `UpdateSourceTrigger=PropertyChanged` to your `Label` binding, but it makes no sense there. It needs to be on both text boxes. – Mike Strobel Sep 14 '17 at 15:14
  • @MikeStrobel Yes, the label shows up as expected. `Content="Test Data"` presents as expected. Also, thanks... I know it's extremely obvious but I'm still figuring some of this stuff out. – Jonny Small Sep 14 '17 at 15:20
  • 1
    Are you manipulating the `add_user_uname` label anywhere in your C# code-behind? Note that if you were to set `add_user_uname.Content`, you would clear the `Binding`. – Mike Strobel Sep 14 '17 at 15:22
  • 3
    Not manipulating, no. The only time it's used is when it's passed to the `User_Management` class to be available for the user creation process. EDIT: WAIT WAIT THERE IS! In my `UI_Navigation` class I have a method for clearing all data from a tab when exiting, which has ` parent.add_user_uname.Content = "";` in it. – Jonny Small Sep 14 '17 at 15:26
  • @JonnySmall WPF rookie mistake :). Best advice I can offer is reading the MSDN docs on dependency properties and/or Chapter 3 of _WPF 4.5 Unleashed_. Then read the similar materials on bindings. – Mike Strobel Sep 14 '17 at 15:34
  • 1
    @MikeStrobel Please post this as an answer so I can mark it as the solution. Commenting out that code fixes the problem and the solution of adding `UpdateSourceTrigger=PropertyChange` does what this code was meant to do anyway. Thank you, sir! – Jonny Small Sep 14 '17 at 15:34
  • Slightly better advice than Mike's: This is why you don't ever set control properties in code in WPF. Use bindings and styles and so on. – 15ee8f99-57ff-4f92-890c-b56153 Sep 14 '17 at 15:37
  • 1
    @EdPlunkett Absolutely, yes, but understanding the subtleties of dependency properties will help save him time in the future, e.g., when he finds himself troubleshooting issues related to precedence in styles/templates/etc. But I strongly agree with the sentiment, and I included that in my answer. – Mike Strobel Sep 14 '17 at 15:48
  • @MikeStrobel Very much agreed, understanding dependency properties is critical. – 15ee8f99-57ff-4f92-890c-b56153 Sep 14 '17 at 15:49