1

Warning: I'm a complete WPF noob coming from a Windows forms background.

I have a WPF UserControl, which in turn contains several other UserControls, each meant to display a certain chunk of data from a user's file. One of these controls simply shows name, address, and customer id.

So, my main control looks like so:

<UserControl x:Class="Plus.Gui.FileView"
             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" 
             mc:Ignorable="d" 
             d:DesignHeight="800" d:DesignWidth="1200" 
             xmlns:gui="clr-namespace:Plus.Gui">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="150*" />
            <RowDefinition Height="150*" />
            <RowDefinition Height="200*" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="488*" />
            <ColumnDefinition Width="260*" />
            <ColumnDefinition Width="252*" />
        </Grid.ColumnDefinitions>
        <gui:pnlDebtor Grid.Column="2" HorizontalAlignment="Stretch" x:Name="pDebtor" VerticalAlignment="Stretch" />
    </Grid>
</UserControl>

The pnlDebtor control is the one that should show my info.

The codebehind for my main control looks like so:

using System.Text;
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;
using MyDataLayer;

namespace Plus.Gui
{
    public partial class FileView : UserControl
    {
        public FileView()
        {
            InitializeComponent();
        }

        public FileView(MyCase file)
        {
            InitializeComponent();
            pDebtor = new pnlDebtor(file);
        }
    }
}

I'm replacing my default pnlDebtor with a new one which loads the data from a file.

Here's the xaml and a part of the codebehind for my pnlDebtor control.

<UserControl x:Class="Plus.Gui.pnlDebtor"
             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" 
             mc:Ignorable="d" 
             d:DesignHeight="230" d:DesignWidth="460">
    <Grid>
        <GroupBox Header="Debtor" HorizontalAlignment="Stretch" Margin="0,0,0,0" Name="groupBox1" VerticalAlignment="Stretch">
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="28" />
                    <RowDefinition Height="56" />
                    <RowDefinition Height="28" />
                    <RowDefinition />
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="80*" />
                    <ColumnDefinition Width="380*" />
                </Grid.ColumnDefinitions>
                <Label Content="Name:" Height="28" HorizontalAlignment="Left" Name="lblName" VerticalAlignment="Top" />
                <Label Content="Address:" Grid.Row="1" Height="28" HorizontalAlignment="Left" Name="lblAddress" VerticalAlignment="Top" />
                <Label Content="Customer Nr.:" Grid.Row="2" Height="28" HorizontalAlignment="Left" Name="lblCustomerNr" VerticalAlignment="Top" />
                <TextBox Grid.Column="1" Height="23" HorizontalAlignment="Stretch" Margin="0,0,0,0" Name="tbName" VerticalAlignment="Top" IsEnabled="False" IsReadOnly="False" />
                <TextBox Grid.Column="1" Grid.Row="1" Height="46" HorizontalAlignment="Stretch" Margin="0,0,0,0" Name="tbAddress" VerticalAlignment="Top" IsReadOnly="False" IsEnabled="True" />
                <TextBox Grid.Column="1" Grid.Row="2" Height="23" HorizontalAlignment="Stretch" Margin="0,0,0,0" Name="tbCustomerNr" VerticalAlignment="Top" IsEnabled="True" IsReadOnly="False" />
                <Button Content="Debtor Details" Grid.ColumnSpan="2" Grid.Row="3" Height="23" HorizontalAlignment="Left" Margin="0,0,0,0" Name="btnDetails" VerticalAlignment="Top" />
            </Grid>
        </GroupBox>
    </Grid>
</UserControl>

public pnlDebtor(MyCase file)
{
    InitializeComponent();
    Contact con = AbstractDataObject.GetObject4ID<Contact>(file.DebtorID);
    string strName = con.Name1;

    if (con.Name2 != null)
        strName += " " + con.Name2;

    if (con.Name3 != null)
        strName += " " + con.Name3;

    if (con.FirstName != null)
        strName += ", " + con.FirstName;

    tbName.Text = strName;
    tbAddress.Text = "test address";
    tbCustomerNr.Text = "test customer id";
}

So, basically, I pass in the file and update the textboxes with the info from the file. Simple. Only, it doesn't work.

If I set a breakpoint after the new pnlDebtor is created, and check the properties, tbName.Text, tbAddress.Text, and tbCustomerNr.Text have indeed been changed. But all the the boxes remain empty in the gui.

If instead of assigning a new pnlDebtor, I simply change the values (pDebtor.tbName.Text = "Dummy") in the codebehind for the FileView control, it works fine.

What am I missing here? Am I not able to replace an existing control with a new one this way?

I've tried setting my original pnlDebtor to null before setting the new control, but got the same results. Somehow my control is being replaced but never being sent to the GUI.

Troy Frazier
  • 391
  • 3
  • 13
  • 1
    You need to read the [Data Binding Overview](https://msdn.microsoft.com/en-us/library/ms752347(v=vs.110).aspx) and [`INotifyPropertyChanged` Interface](https://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged(v=vs.110).aspx) pages on MSDN. – Sheridan Apr 22 '15 at 13:55

2 Answers2

1

While you can create controls from code, you should stop thinking in that Windows forms way of creating UI, and read more about how WPF works in general and about data binding.

Basically, you need to bind text box value to string property, and then use the code only to set values of those properties, binding will take care of the rest and update the UI for you.

Nemanja Banda
  • 796
  • 2
  • 14
  • 23
  • It works perfectly. Thank you. When I gave my pnlDebtor control a DebtorName property and set the Text property of my tbName to: `Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=my:pnlDebtor, AncestorLevel=1}, Path=DebtorName, Mode=OneWay}"` Everything was handled for me. No need to implement INotifyPropertyChanged It bothers me that the binding is done in XAML instead of codebehind. It also bothers me that if I set a breakpoint, the pnlDebtor.tbName.Text property remains empty. – Troy Frazier Apr 22 '15 at 15:42
  • Also, instead of creating a new pnlDebtor, I keep the existing control and simply assign the file to a property of the existing control. – Troy Frazier Apr 22 '15 at 15:51
  • 1
    To be honest, you should try to use property, I know it feels strange coming from WinForms, but once you see the benefits, you will begin to appreciate them. For example, when working in a team, it allows for UI design to be completely separated from the model, so the person designing the UI needs to know only the names of properties and isn't bothered with actual implementation in the code. Leads to a much easier and cleaner UI design too. – Nemanja Banda Apr 22 '15 at 15:52
  • That's what I don't get. I thought the whole point was to separate UI and code, which was my intent with my original code. This way, the code linking my Textbox to my data is in the xaml instead of the codebehind (where I think it should be). – Troy Frazier Apr 22 '15 at 16:03
  • 1
    It is separated, since UI code is only in XAML. If you set binding in your codebehind, or want to directly change the value of textbox, you would have to know the name of UI control and reference it from the code. With just binding, your code doesn't care for what the data is being used for, it works the same, whether some UI code is reading the properties or not. And that makes it separated, as soon as you reference controls from the code, it no longer is :) – Nemanja Banda Apr 23 '15 at 08:50
0

Looks like you want to use data binding. You can specify the data context for binding in the xaml, or to keep it similar to your existing code you can do it in the code behind:

DataContext = AbstractDatenObjekt.GetObjekt4ID<Contact>(file.DebtorID);

XAML:

<TextBox Text="{Binding strName, Mode=TwoWay}" Grid.Column="1" Height="23" HorizontalAlignment="Stretch" Margin="0,0,0,0" Name="tbName" VerticalAlignment="Top" IsEnabled="False" IsReadOnly="False" />

In your contact class you need to implement INotifyPropertyChanged or use a base case like https://stackoverflow.com/a/1316417/2622972. Once you have that, add a property for strName that calls your version of however you do INotifyPropertyChanged.

Community
  • 1
  • 1
matt
  • 333
  • 2
  • 10
  • 23