0

I want to display the data from my TelemetryDataPoint inside the VM to the View, just for extra information, the TelemetryDataPoint received the data from my Helper clas. I already tried with my code below, but somehow the data won't displayed to my View but if I debug TelemetryDataPoint it has the value on it.

TelemetryDataPointVM.cs

public class TelemetryDataPointVM : INotifyPropertyChanged
{       
    private TelemetryDataPoint? telemetryDataPoint;
   
    public TelemetryDataPoint? TelemetryDataPoint
    {
        get => telemetryDataPoint;
        set
        {
            // when I checked the value below it has the value
            telemetryDataPoint = value;
            OnPropertyChanged(nameof(TelemetryDataPoint));
        }
    }
    public TelemetryDataPointVM()
    {
        
    }
    public event PropertyChangedEventHandler? PropertyChanged;
    private void OnPropertyChanged(string propertyName) 
        => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

HelperClass.cs

public class GetPortHelper
    { 
        TelemetryDataPointVM TelemetryDataPointVM { get; set; }

        public GetPortHelper()
        {
            TelemetryDataPointVM = new();
        }

        private void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
        {
            SerialPort sp = (SerialPort)sender;

            if(sp.IsOpen)
            {
                string DataString = sp.ReadLine();
                string[] arrayDataString = DataString.Split(",");

                if(arrayDataString[3] == "C")
                {
                   TelemetryDataPointVM.TelemetryDataPoint = ParseToTelemetryData(arrayDataString);
                }
                else if(arrayDataString[3] == "Y")
                {
                    //ParseToTetheredData(arrayDataString);
                }
             }
         }

    }

Altitude.xaml

<UserControl x:Class="GUI_Cansat.View.Altitude"
        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:GUI_Cansat.View"
        xmlns:vm="clr-namespace:GUI_Cansat.ViewModel"
        mc:Ignorable="d" 
        d:DesignHeight="450" d:DesignWidth="800">
<UserControl.DataContext>
    <vm:TelemetryDataPointVM/>
</UserControl.DataContext>
<Grid>
    <Label  Content="{Binding TelemetryDataPoint.Altitude, Mode=TwoWay}"
            ContentStringFormat="Altitude:      {0} M"
            Style="{StaticResource fontMain}"
            VerticalAlignment="Center" FontSize="14"/>
</Grid>

Update 1:

I assembled my Altitude in my MainWindows like this:

<Border Style="{StaticResource borderMain}"
                Grid.Row="8">
            <view:Altitude  x:Name="Altitude" />
        </Border>

Should I put the DataContext inside this <view:Altitude/>? If I put the code like this and {Binding TelemetryDataPointVM}, my VS told me "No Data context found for binding"

Wavesolid
  • 175
  • 11
  • Use the "XAML Binding Errors" window in Visual Studio. What do you see in there? (It was added in VS 2019 v16.7, see here: https://devblogs.microsoft.com/visualstudio/improvements-to-xaml-tooling-in-visual-studio-2019-version-16-7-preview-1/ ) – Dai Dec 11 '21 at 09:57
  • Protip: Use the `this.` keyword in C# to make it clear to people-who-read-your-code when an identifier is an instance member or not. – Dai Dec 11 '21 at 09:58
  • Your property `set` logic should only call `OnPropertyChanged` when the property value actually changes, right now you're calling it whenever the setter is invoked: this is incorrect, and can potentially cause infinite-loops (e.g. if one setter is chained to another). – Dai Dec 11 '21 at 10:00
  • `` <-- This is likely wrong and is likely the cause of the problem because the ViewModel being used is not the same instance that you're referencing in your `GetPortHelper.TelemetryDataPointVM` property. – Dai Dec 11 '21 at 10:02
  • Hey thanks for the reply. I checked that `XAML Binding Errors` under option but I can't see that setting (currently I'm using VS 2022) @Dai – Wavesolid Dec 11 '21 at 10:02
  • that's what I'm afraid about, using `` was a mistake – Wavesolid Dec 11 '21 at 10:05
  • Yes, remove the `` element entirely. You can still have design-time intellisense for your ViewModel by using `d:DataContext="{d:DesignInstance Type=YourViewModelType, IsDesignTimeCreatable=True}` (or `IsDesignTimeCreatable=False` depending on your project). – Dai Dec 11 '21 at 10:08
  • thanks Dai for your comment, but can you give me some examples? because I'm kinda unfamiliar with the `design-time intellisense` like what you said – Wavesolid Dec 11 '21 at 10:14
  • I suggest reading this: https://stackoverflow.com/questions/29399157/how-do-i-specify-datacontext-viewmodel-type-to-get-design-time-binding-checkin – Dai Dec 11 '21 at 10:29
  • I checked that thread and try to implement it, but somehow it gave me an error message: `Could not load file or assembly 'GUI_Cansat, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified.` – Wavesolid Dec 11 '21 at 10:34

1 Answers1

1

The problem here is that you have two instances of the view model class, one created by

<UserControl.DataContext>
    <vm:TelemetryDataPointVM/>
</UserControl.DataContext>

and one by

TelemetryDataPointVM = new();

in the GetPortHelper constructor.

A UserControl, like any other control, should never explicitly set its own DataContext (and thus have its own "private" view model). It should instead inherit the DataContext from the main view in which it lives, e.g. a MainWindow.

Remove the DataContext assignment from the UserControl's XAML, and assign a GetPortHelper instance to the DataContext of the MainWindow, e.g. like

private readonly GetPortHelper portHelper = new GetPortHelper();

public MainWindow()
{
    InitializeComponent();
    DataContext = portHelper;
}

Now you would declare the UserControl in the MainWindow's XAML like

<local:Altitude DataContext="{Binding TelemetryDataPointVM}" />

A typical use case of control like this - which operates on a specific view model - would be a DataTemplate like

<DataTemplate DataType="{x:Type vm:TelemetryDataPointVM}">
    <local:Altitude />
</DataTemplate>

where the DataContext is inherited from a ContentControl or ContentPresenter that has a TelemetryDataPointVM instance as its Content, and that automatically applies the above DataTemplate according to its DataType, e.g.

<ContentControl Content="{Binding TelemetryDataPointVM}" />

You could however improve your control by making it independent of a specific view model. The control would expose a set of bindable properties, e.g. an AltitudeValue property and you would then use it like this:

<local:Altitude AltitudeValue="{Binding TelemetryDataPointVM.Altitude}" />

In order to accomplish this, the AltitudeValue property would have to be declared as dependency property, and a UI element in the UserControl's XAML would bind to it via a RelativeSource Binding like this:

<TextBlock Text="{Binding AltitudeValue,
    RelativeSource={RelativeSource AncestorType=UserControl}}" />
Clemens
  • 123,504
  • 12
  • 155
  • 268
  • Thanks @Clemens anyway, I unable to bind that `TelemetryDataPointVM` in my Mainwindow even I already initiate the `GetPortHelper` under `MainWindow` – Wavesolid Dec 11 '21 at 11:19
  • 1
    Not sure what exactly "unable to bind" means. Are there any data binding error messages in the Output Window in Visual Studio when you debug the application? – Clemens Dec 11 '21 at 11:45
  • When I try to Bind it the intellisense didn't give me the `TelemetryDataPointVM` and there is no option that refer to `GetPortHelper` object – Wavesolid Dec 12 '21 at 03:36
  • Besides Intellisense, it would be interesting to know if the bindings do work at all when you run your application. Your oroiginal problem of having two view model objects should be solved if you follow the explanations in the answer. – Clemens Dec 12 '21 at 09:21
  • hello sorry for late replying this comment, I just want to ask about this code `` I updated my post with my question. please kindly to check it – Wavesolid Dec 18 '21 at 05:57
  • Please do not modify the question or ask another question in the same post when it has already been answered. If the answer is helpful, accept it, and ask a new question on StackOverflow. – Clemens Dec 18 '21 at 08:53
  • actually the question still related with the post, I'm still can't solved this problem – Wavesolid Dec 18 '21 at 09:17
  • It should all be obvious from this answer. The only point where you set a DataContext in your application is in the MainWindow constructor. The value of the DataContext is then inherited by all child elements up the visual tree, including your UserControl. Do not get confused when the XAML designer complains, but rather see if your application works or not. Does it? – Clemens Dec 18 '21 at 09:23
  • What does that have to do with your original question? Haven't you tried to search that error message on StackOverflow? There are plenty of related posts. – Clemens Dec 18 '21 at 09:41