3

I'm working on a UWP App using the Template 10, and I don't manage to get the UI updated after a property is changed in the ViewModel. I tried to implement the Bindable base at the Model, but still doesn't work.

XAML:

<Page.DataContext>
    <vm:RoomPageViewModel x:Name="ViewModel" />
</Page.DataContext>

<Grid x:Name="RoomProperties"
    RelativePanel.Below="pageHeader"
    RelativePanel.AlignLeftWithPanel="True">
    <Grid.ColumnDefinitions>
        <ColumnDefinition></ColumnDefinition>
        <ColumnDefinition></ColumnDefinition>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition></RowDefinition>
        <RowDefinition></RowDefinition>
        <RowDefinition></RowDefinition>
    </Grid.RowDefinitions>
    <Image Grid.Column="0" Width="220" Height="220" Stretch="Fill" Source="{x:Bind ViewModel.Room.Image}"></Image>
    <TextBlock Grid.Column="1" FontSize="16" Text="{x:Bind ViewModel.Room.Name}"></TextBlock>
    <TextBlock Grid.Column="0" Grid.Row="1" FontSize="16" Text="Room Type: "></TextBlock>
    <TextBlock Grid.Column="1" Grid.Row="1" FontSize="16" Text="{x:Bind ViewModel.Room.Type}"></TextBlock>
    <TextBlock Grid.Column="0" Grid.Row="2" FontSize="16" Text="Room Number: "></TextBlock>
    <TextBlock Grid.Column="1" Grid.Row="2" FontSize="16" Text="{x:Bind ViewModel.Room.Number}"></TextBlock>
</Grid>
<ListView x:Name="SensorListView"
          ItemsSource="{x:Bind ViewModel.Room.Sensors}"
          IsEnabled="False"
          RelativePanel.Below="RoomProperties"
          RelativePanel.AlignLeftWithPanel="True">
    <ListView.ItemTemplate>
        <DataTemplate x:DataType="data:Sensor">
            <StackPanel HorizontalAlignment="Left">
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition></ColumnDefinition>
                        <ColumnDefinition></ColumnDefinition>
                        <ColumnDefinition></ColumnDefinition>
                    </Grid.ColumnDefinitions>
                    <TextBlock Grid.Column="0" FontSize="16" Text="{x:Bind Name}"></TextBlock>
                    <TextBlock Grid.Column="1" FontSize="16" Text="{x:Bind SensorValues[0].Value, Mode=TwoWay}"></TextBlock>
                    <TextBlock Grid.Column="2" FontSize="16" Text="{x:Bind Units}"></TextBlock>
                </Grid>
            </StackPanel>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

ViewModel:

public class RoomPageViewModel : ViewModelBase
{
    Template10.Services.SerializationService.ISerializationService _SerializationService;
    private FileIOHelper.FileIOHelper fileIOHelper = new FileIOHelper.FileIOHelper();
    private AppServiceConnection serialCommandService;

    // This method is called by the Set accessor of each property.
    // The CallerMemberName attribute that is applied to the optional propertyName
    // parameter causes the property name of the caller to be substituted as an argument.

    public RoomPageViewModel()
    {
        if (Windows.ApplicationModel.DesignMode.DesignModeEnabled)
        {
            Room = Room.CreateNewRoom();
        }
    }

    private Room room = Room.CreateNewRoom();
    public Room Room
    {
        get
        {
            return this.room;
        }

        set
        {
            Set(ref room, value);
        }
    }

    public void UpdateRoom()
    {
        foreach (var sensor in Room.Sensors)
        {
            var sensorValue = new SensorValue();
            sensorValue.Sensor = "R" + Room.Number + "D" + sensor.DeviceNumber + "S" + sensor.Type;
            ObservableCollection<SensorValue> newList = fileIOHelper.ReadFromFile(sensorValue).ToObservableCollection();
            SensorValue newSensorValue = newList.Last();
            sensor.SensorValues = new ObservableCollection<SensorValue> { newSensorValue };
        }
        foreach (var actuator in Room.Actuators)
        {
            var actuatorValue = ActuatorValue.CreateNewActuatorValue();
            actuatorValue.Actuator = "R" + Room.Number + "D" + actuator.DeviceNumber + "A" + actuator.Type;
            ObservableCollection<ActuatorValue> newList = fileIOHelper.ReadFromFile(actuatorValue).ToObservableCollection();
            ActuatorValue newActuatorValue = newList.Last();
            actuator.ActuatorValues = new ObservableCollection<ActuatorValue> { newActuatorValue };
        }
    }

    public async void RefreshButton_Click(object sender, object parameter)
    {
        Random rnd = new Random();
        Room = Room.CreateNewRoom(rnd.Next(1, 9));
        //UpdateRoom();
        await Task.CompletedTask;
    }

Model:

public class Room : BindableBase
{
    private string name;
    private string image;
    private string type;
    private int number; 

    public string Name
    {
        get
        {
            return name;
        }
        set
        {
            Set(ref name, value);
        }
    }

    public string Image
    {
        get
        {
            return image;
        }
        set
        {
            Set(ref image, value);
        }
    }

    public string Type
    {
        get
        {
            return type;
        }
        set
        {
            Set(ref type, value);
        }
    }

    public int Number
    {
        get
        {
            return number;
        }
        set
        {
            Set(ref number, value);
        }
    }

    private ObservableCollection<Sensor> sensors;

    private ObservableCollection<Actuator> actuators;

    public ObservableCollection<Sensor> Sensors
    {
        get
        {
            return sensors;
        }
        set
        {
            Set(ref sensors, value);
        }
    }

    public ObservableCollection<Actuator> Actuators
    {
        get
        {
            return actuators;
        }
        set
        {
            Set(ref actuators, value);
        }
    }

    public Room() {
        Random rnd = new Random();
        Name = "DefaultName";
        Image = "DefaultImage";
        Type = "DefaultType";
        Number = rnd.Next(1,9);
        Sensors = new ObservableCollection<Sensor>();
        Actuators = new ObservableCollection<Actuator>();
    }

    public Room(int inputNumber)
    {
        Name = "DefaultName";
        Image = "DefaultImage";
        Type = "DefaultType";
        Number = inputNumber;
        Sensors = new ObservableCollection<Sensor>();
        Actuators = new ObservableCollection<Actuator>();
    }

    public static Room CreateNewRoom(int inputNumber)
    {
        return new Room(inputNumber);
    }
}

I used this guide for documentation for the implementation (https://github.com/Windows-XAML/Template10/wiki/MVVM). Any idea on why the UI is not getting updated? Thanks.

Bart
  • 9,925
  • 7
  • 47
  • 64
Carlos
  • 85
  • 5
  • After further investigations, I read the problem might be that the Bindings need to be OneWay, as this is not default value. I'll try later when I have some time. – Carlos Nov 23 '16 at 07:54

1 Answers1

2

A mistake most people (including myself) often make when being used to the 'old' Binding syntax is that x:Bind has OneTime binding as default instead of OneWay binding.

Mode: Specifies the binding mode, as one of these strings: "OneTime", "OneWay", or "TwoWay". The default is "OneTime". Note that this differs from the default for {Binding}, which is "OneWay" in most cases.

Source: MSDN

What you need for your binding updates to work is:

  • Using INotifyPropertyChanged, this is handled by BindableBase.
  • Set the correct mode, e.g.

    Text="{x:Bind ViewModel.Room.Number, Mode=OneWay}

Bart
  • 9,925
  • 7
  • 47
  • 64
  • Thank for your help. Now updates for the main properties work, altough if I change something inside one of the elements of the ObservableCollections, it doesn't update the UI. The structure I have is: Room(Properties + Actuator ObservableColletion) => Actuator (Properties + ActuatorStatus ObservableColletion). Although I have implemented BindableBase in both Actuator and ActuatorStatus Models, the UI doesn't get updated. All the Bindings are now OneWay. – Carlos Nov 23 '16 at 19:09
  • For some reason, inside the ListView, using "{x:Bind xxx, Mode=OneWay}" doesn't let the UI to get updated, but "{Binding xxx, Mode=OneWay}" works perfectly. Thanks again for your help. – Carlos Nov 23 '16 at 19:32
  • @Carlos can you repro that x:bind behavior? If you can, reliably, send me a link to the repro project and I will tell the team about it. Email me @ Jerry.Nixon at Microsoft.com if you can. – Jerry Nixon Dec 09 '16 at 16:32