2

I'm not a smart man. I've spent hours reading a number of different previously asked questions and trying to get this thing to work, but I am still missing something and I'm not sure what it is. I will probably be embarrassed when I realize what it is...but I got the impression from the 2nd link below that making things update any other way really shouldn't be done.

These are the things I have already read:

WPF databinding not updating

How do I refresh visual control properties

Refreshing a WPF window on demand

Data binding overview on MSDN

I'm trying to one-way bind a textblock to a string source and have it update automatically as my code runs...but it never seems to update. As for all of the objects I'm using...my original desire with starting to learn C# was to create my own program that could stream video from an input stream of any type over the internet to my phone...obviously I am a long way from that. Your help is greatly appreciated!

XAML

  <Window x:Class="WpfApp1.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:c="clr-namespace:WpfApp1"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <c:dataHolder x:Key="source"/>
    </Window.Resources>
    <Window.DataContext>
        <Binding Source="{StaticResource source}"/>
    </Window.DataContext>

    <Grid>
        <TextBox x:Name="tb1" HorizontalAlignment="Left" Height="22" 
Margin="45,35,0,0" TextWrapping="Wrap" Text="Enter IP" 
VerticalAlignment="Top" Width="195"/>
        <Button x:Name="Connect" Content="Connect" 
HorizontalAlignment="Left" Margin="390,239,0,0" VerticalAlignment="Top" 
Width="75" Click="Connect_Click"/>
        <TextBlock x:Name="mblock" Text="{Binding Path=Message, Mode=OneWay, 
UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" Height="47" 
Margin="45,105,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="195"/>
    </Grid>
</Window>

Code Behind

public partial class MainWindow : Window
{
    private dataHolder dh;
    public MainWindow()
    {
        dh = new dataHolder();
        dh.Message = "Initialize";
        InitializeComponent();
    }

    Binding myBinding = new Binding("myDataProperty");

    private void Connect_Click(object sender, RoutedEventArgs e)
    {
        TcpListener server = null;
        try
        {
            myBinding.Source = dh;
            mblock.SetBinding(TextBlock.TextProperty, myBinding);
            //Set TcpListener on port 13000.
            Int32 port = 13000;
            IPAddress localAddr = IPAddress.Parse("192.168.32.137");
            server = new TcpListener(localAddr, port);
            server.Start();
            Byte[] bytes = new Byte[256];
            String data = null;
            //Enter the listening loop.
            while (true)
            {
                dh.Message = "Waiting for a connection";
                TcpClient client = server.AcceptTcpClient();
                data = null;
                NetworkStream stream = client.GetStream();
                int i;
                while ((i = stream.Read(bytes, 0, bytes.Length)) != 0)
                {
                        data = System.Text.Encoding.ASCII.GetString(bytes,0, 
i);
                        dh.Message = "Received:" + data;
                        data = data.ToUpper();
                        data = data + "Sucka";
                        byte[] msg = 
System.Text.Encoding.ASCII.GetBytes(data);
                        stream.Write(msg, 0, msg.Length);
                        dh.Message = "Sent:" + data;
                    }
                    client.Close();
                }
            }
            catch (SocketException ex)
            {
                string nastyE;
                nastyE = ex.Message;
                dh.Message = "Socket Exception" + nastyE;
            }
            finally
            {
                server.Stop();
            }
        }
    }

dataHolder

public partial class dataHolder : INotifyPropertyChanged
{
    private string message;
    public string Message
    {
        get
        {
            return message;
        }
        set
        {
            message = value;
            NotifyPropertyChanged("Message");
        }
    }
    public event PropertyChangedEventHandler PropertyChanged;
    private void NotifyPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}
Community
  • 1
  • 1
James
  • 41
  • 6

2 Answers2

4

The main problem you have is that your being redundant, which is likely to happen when you are using several examples that choose different ways to perform the same task and don't have the experience to realise that they are the same

  • you are creating 2 data holders, one for the XAML one for the CodeBehind which as they aren't the same one mean your back end doesn't update your front end.

  • and trying to do the binding in several different ways

i would suggest you read this guide to MVVM as it is a very easy to understand explanation

Starting with the XAML

you don't need to specify the namespaces twice

xmlns:c="clr-namespace:WpfApp1"
xmlns:local="clr-namespace:WpfApp1"

just pick one either c or local

then this

<Window.Resources>
    <c:dataHolder x:Key="source"/>
</Window.Resources>
<Window.DataContext>
    <Binding Source="{StaticResource source}"/>
</Window.DataContext>

and the constructor logic

private dataHolder dh;
public MainWindow()
{
    dh = new dataHolder();
    dh.Message = "Initialize";
    InitializeComponent();
}

can be shortened to

<Window.DataContext>
    <c:dataHolder Message="Initialize"/>
</Window.DataContext>

if you need to access from the code behind add this property

public dataHolder dh => DataContext as dataHolder ;

though you usually don't, use the ICommand interface to bind actions to your VM directly this would look something like this

public class CallbackCommand : ICommand
{
    public event EventHandler CanExecuteChanged;

    public bool CanExecute(object parameter)
        => CanExecuteCallback?.Invoke(parameter) ?? true;

    public void Execute(object parameter)
        => Callback(parameter);

    private Action<object> _Callback;

    public Action<object> Callback
    {
        get { return _Callback; }
        set
        {
            _Callback= value;
            CanExecuteChanged?.Invoke(this, EventArgs.Empty);
        }
    }
    private Predicate<object> _CanExecuteCallback;
    public Predicate<object> CanExecuteCallback
    {
        get { return _CanExecuteCallback; }
        set
        {
            _CanExecuteCallback= value;
            CanExecuteChanged?.Invoke(this, EventArgs.Empty);
        }
    }
}

(there are numerous versions of this already available PRISMS's DelegateCommand is complete and relatively simple)

then in your data Holder class you have a property

public CallbackCommand Connect {get;} = new CallbackCommand ()
{
    Callback = <<your dataHolders connect method>>
}

your Xaml would then bind like this

<Button Content="Connect" Command="{binding Connect}"/>

which brings us nicely to the subject of binding, binding needs to be done in Xaml or Code behind not both so as you have both but the code behind is incorrect, i would suggest using the XAML alone

doing all this will reduce your Code behind to this

public MainWindow()
{
    InitializeComponent();
}
MikeT
  • 5,398
  • 3
  • 27
  • 43
2

Your issue is that you are creating two dataholder instances and manipulating one while binding to the other.

The first instance is created by you in the Window constructor aka field dh and the second instance is created by the following XAML:

<Window.Resources>
    <c:dataHolder x:Key="source"/>
</Window.Resources>

To fix your issue quickly, rather define the Window constructor dh field as DH the public property.

   public dataholder DH {get; set;}

...then in your Window XAML bind Window.DataContext as follows:

<Window 
  <!-- all the other attributes -->
  DataContext="{Binding DH, RelativeSource={RelativeSource Self}}">
toadflakz
  • 7,764
  • 1
  • 27
  • 40
  • creating it in xaml is usually preferable to CodeBhind aschanging the windows datacontext with a new datahandler would break the link again – MikeT Apr 05 '17 at 15:08
  • He's not asking for the best way to do this. The best way would be to use MVVM and Commands. However, all his code is dependent on his code-behind event handlers manipulating the field which is why I recommended this solution - less to change. – toadflakz Apr 05 '17 at 16:07
  • the OP is clearly new to WPF probably coming from winforms, so doesn't know best practice, our job as the more experienced developers is to guide novices onto the right path, you answer is perfectly correct in that is fixes his problem but could lead him into make the same error in a different manner, hence my comment highlighting that issue – MikeT Apr 05 '17 at 16:17