Observablecollection from interface add to listview via binding - c#

I have an application that draws use of Xabre.ble plugin.
When the application scans for devices, once a device is discovered the application appends it to an ObservableCollection which derives from an interface.
I have made a Data template in Xaml that i wish to populate, but for some reason i can't seem to get the BindingContext right.
Basically, what I want to achieve, is to add device objects to my ObservableCollection IDevice, and fetch just the name of the individual devices in the bindingcontext for the user to see in the listview
Xaml:
<ListView x:Name="deviceList">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<local:IconView Grid.Row="0" Grid.Column="0" Source="BLE.png" Foreground="#3b5998" WidthRequest="30" HeightRequest="30"/>
<Label Grid.Row="1" Text="{Binding DeviceName}" TextColor="Teal"/>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Cs:
public ObservableCollection<IDevice> Devices { get; set; }
IAdapter adapter = CrossBluetoothLE.Current.Adapter;
public ObservableCollection<string> DeviceName { get; set; }
public MainPage()
{
//Instantiate observable collection
Devices = new ObservableCollection<IDevice>();
DeviceName = new ObservableCollection<string>();
//Appending peripherals to list
BindingContext = DeviceName;
deviceList.ItemsSource = BindingContext;
Content = deviceList;
Refreshcmd();
}
public void Refreshcmd()
{
//DeviceDiscovered is an event, when it discovers a device, we fire the eventhandler
adapter.DeviceDiscovered += (s, a) =>
{
if (a.Device.Name != null)
{
Devices.Add(a.Device);
DeviceName.Add(a.Device.Name);
}
};
adapter.StartScanningForDevicesAsync();
}
As you can see, I have tried to make an object which contains a string with just the name of the IDevice. However, it doesn't append to my databinding DeviceName.
Furthermore, I don't really like this solution, as I would much rather be able to access the Devices.Name from the interface, but that is not allowed apparently.
- the reason why I would much rather like that solution is that the user eventually will have to connect to a device from the list, meaning that it is much easier to have one object for all the "behind the scenes" details.

You should refactor your code, to a MVVM approach.
namespace YourNameSpace.ViewModels
{
public class YourViewModel
{
private ObservableCollection<IDevice> devices;
public ObservableCollection<IDevice> Devices
{
get { return devices; }
set
{
devices = value;
}
}
public YourViewModel()
{
Devices = new ObservableCollection<IDevice>();
}
}
}
In your Page.xaml.cs
public partial class YourPage : ContentPage
{
public YourPage()
{
InitializeComponent();
BindingContext = new YourViewModel();
}
}
public void Refreshcmd()
{
//DeviceDiscovered is an event, when it discovers a device, we fire the eventhandler
adapter.DeviceDiscovered += (s, a) =>
{
if (a.Device.Name != null)
{
(YourViewModel)Devices.Add(a.Device);
//DeviceName.Add(a.Device.Name);
}
};
adapter.StartScanningForDevicesAsync();
}
Then in Page.xaml
<ListView ItemsSource="{Binding Devices}"
CachingStrategy="RecycleElement">

Related

Some confusion about MVVM data binding in a multi-tabbed interface

I am an experienced developer, but relative newcomer to the world of WPF and MVVM. I’ve been reading up on various tutorials and examples of following the MVVM pattern. I am working on converting an existing MDI Windows forms (a student/class management system) application into WPF. My basic design is for a menu (tree view) docked on the left side of the main window with a tab control that would contain the different views (student, class, teacher, billing etc) that the user requires. As proof of concept (and to get my head around WPF) I have the following:
A simple model, Student
public class Student
{
public DateTime BirthDate { get; set; }
public string Forename { get; set; }
public int Id { get; set; }
public string Surname { get; set; }
public override string ToString()
{
return String.Format("{0}, {1}", Surname, Forename);
}
}
The StudentViewModel
public class StudentViewModel : WorkspaceViewModel
{
private Student student;
public override string DisplayName
{
get
{
return String.Format("{0} {1}", student.Forename, student.Surname);
}
}
public string Forename
{
get
{
return student.Forename;
}
set
{
student.Forename = value;
RaisePropertyChanged();
RaisePropertyChanged("DisplayName");
}
}
public int Id
{
get
{
return student.Id;
}
set
{
student.Id = value;
RaisePropertyChanged();
}
}
public string Surname
{
get
{
return student.Surname;
}
set
{
student.Surname = value;
RaisePropertyChanged();
RaisePropertyChanged("DisplayName");
}
}
public StudentViewModel()
{
this.student = new Student();
}
public StudentViewModel(Student student)
{
this.student = student;
}
}
The view model inherits WorkspaceViewModel, an abstract class
public abstract class WorkspaceViewModel : ViewModelBase
{
public RelayCommand CloseCommand { get; set; }
public event EventHandler OnClose;
public WorkspaceViewModel()
{
CloseCommand = new RelayCommand(Close);
}
private void Close()
{
OnClose?.Invoke(this, EventArgs.Empty);
}
}
This in turn inherits ViewModelBase, where I implement INotifyPropertyChanged. The RelayCommand class is a standard implementation of the ICommand interface.
The MainWindowViewModel holds a collection of Workspaces
public class MainViewModel : WorkspaceViewModel
{
private WorkspaceViewModel workspace;
private ObservableCollection<WorkspaceViewModel> workspaces;
public WorkspaceViewModel Workspace
{
get
{
return workspace;
}
set
{
workspace = value;
RaisePropertyChanged();
}
}
public ObservableCollection<WorkspaceViewModel> Workspaces
{
get
{
return workspaces;
}
set
{
workspaces = value;
RaisePropertyChanged();
}
}
public RelayCommand NewTabCommand { get; set; }
public MainViewModel()
{
Workspaces = new ObservableCollection<WorkspaceViewModel>();
Workspaces.CollectionChanged += Workspaces_CollectionChanged;
NewTabCommand = new RelayCommand(NewTab);
}
private void NewTab()
{
Student student = new Student();
StudentViewModel workspace = new StudentViewModel(student);
Workspaces.Add(workspace);
Workspace = workspace;
}
private void Workspaces_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null && e.NewItems.Count != 0)
{
foreach (WorkspaceViewModel workspace in e.NewItems)
{
workspace.OnClose += Workspace_OnClose; ;
}
}
if (e.OldItems != null && e.OldItems.Count != 0)
{
foreach (WorkspaceViewModel workspace in e.OldItems)
{
workspace.OnClose -= Workspace_OnClose;
}
}
}
private void Workspace_OnClose(object sender, EventArgs e)
{
var workspace = (WorkspaceViewModel)sender;
Workspaces.Remove(workspace);
}
}
The StudentView xaml
<UserControl x:Class="MvvmTest.View.StudentView"
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:MvvmTest.View"
xmlns:vm="clr-namespace:MvvmTest.ViewModel"
mc:Ignorable="d">
<UserControl.DataContext>
<vm:StudentViewModel/>
</UserControl.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0" Text="ID:"/>
<TextBlock Grid.Column="0" Grid.Row="1" Text="Forename:"/>
<TextBlock Grid.Column="0" Grid.Row="2" Text="Surname:"/>
<TextBlock Grid.Column="0" Grid.Row="3" Text="Date of Birth:"/>
<TextBox Grid.Column="1" Grid.Row="0" Text="{Binding Id, Mode=TwoWay}"/>
<TextBox Grid.Column="1" Grid.Row="1" Text="{Binding Forename, Mode=TwoWay}"/>
<TextBox Grid.Column="1" Grid.Row="2" Text="{Binding Surname, Mode=TwoWay}"/>
<DatePicker Grid.Column="1" Grid.Row="3" SelectedDate="{Binding BirthDate, Mode=TwoWay}"/>
</Grid>
</UserControl>
The StudentViewModel and StudentView are linked via a resource dictionary in App.xaml
<ResourceDictionary>
<vm:MainViewModel x:Key="MainViewModel"/>
<DataTemplate DataType="{x:Type vm:StudentViewModel}">
<v:StudentView/>
</DataTemplate>
</ResourceDictionary>
And finally, the MainWindow view (goal is that this will eventually conform to MVVM in that the MainWindowViewModel will define the menu structure)
<Window x:Class="MvvmTest.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:local="clr-namespace:MvvmTest"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:vm="clr-namespace:MvvmTest.ViewModel"
xmlns:v="clr-namespace:MvvmTest.View"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<vm:MainViewModel/>
</Window.DataContext>
<DockPanel>
<StackPanel DockPanel.Dock="Left" Orientation="Vertical">
<Button Content="New Student">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding NewTabCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</StackPanel>
<TabControl ItemsSource="{Binding Workspaces}" SelectedItem="{Binding Workspace}">
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding DisplayName, Mode=OneWay}"/>
<Button>X</Button>
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<StackPanel>
<UserControl Content="{Binding}"/>
</StackPanel>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</DockPanel>
</Window>
When I click the ‘New student’ button a new student workspace is created, added to Workspaces collection and displays in the TabControl. All seems well. But when I enter data on the view I noticed that the tab header isn’t updated. First sign that all is not working as it should...
Then when I click ‘New student’ a second time. Another workspace is created, but that duplicates the values entered in the first. Further, when editting the second tab, the first is also updated.
Placing a breakpoint into the NewTab method revealed that although the Workspaces collection holds StudentViewModels, the display properties are still null; even though the StudentView appears to hold data.
After much puzzling I discovered that if I do not set the data context on the StudentView xaml then the binding behaves properly and the test app works as expected. But then doesn't that mean the xaml designer isn't really validating the display property bindings, even though at runtime the path is resolved?
Anyway, I’m now left a few questions. How and why does what I've done work? It essentially appears to go against everything I’ve read and seen on MVVM. Furthermore when trying to apply this application to a MVVM framework (eg MVVM Light) the views are explicitly defined with the data context set in the xaml (eg: DataContext="{Binding Path=Student, Source={StaticResource Locator}}). Which makes even less sense...
As I said, what I’ve got does work, but I’m not really understanding why, and therefore doubt is clawing away that I’ve done something wrong. As a result I’m reluctant to proceed further on serious development from fear of having to rework later (having dug myself into a hole).
Child controls automatically inherit DataContext from their parent. So if no DataContext is specified in the UserControl then each instance uses the instance of StudentViewModel contained in the WorkSpaces Collection. On the other hand when specifing the datacontext in the UserControl XAML each instance of the view is bound the same ViewModel instance. That is why changing data on one view results in changes on all other views. The views are all referencing the same object. I hope that is clear.

How to bind two Views side by side

I been following this Walkthrough which does a great job of explaining switching between two views, plus more.
What I'm trying to adapt the project to do is, instead of switching between two views, is show two views side by side.
Andy set up the following in his MainWindowViewModel placing ViewModels into an OC:
public class MainWindowViewModel : NotifyUIBase
{
public ObservableCollection<ViewVM> Views {get;set;}
public MainWindowViewModel()
{
ObservableCollection<ViewVM> views = new ObservableCollection<ViewVM>
{
new ViewVM{ ViewDisplay="Customers", ViewType = typeof(CustomersView), ViewModelType = typeof(CustomersViewModel)},
new ViewVM{ ViewDisplay="Products", ViewType = typeof(ProductsView), ViewModelType = typeof(ProductsViewModel)}
};
Views = views;
RaisePropertyChanged("Views");
views[0].NavigateExecute();
}
}
In MainWindow.xaml.cs navigation calls ShowUserControl() to set the view
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// Apply default form level font style
Style = (Style)FindResource(typeof(Window));
Messenger.Default.Register<NavigateMessage>(this, (action) => ShowUserControl(action));
this.DataContext = new MainWindowViewModel();
}
private void ShowUserControl(NavigateMessage nm)
{
EditFrame.Content = nm.View;
}
}
My Code:
I won't be needing them in an OC and I won't be switching between views, they will be displayed at the same time side-by-side. So I was thinking what I need to do is
public class MainWindowViewModel : NotifyUIBase
{
private ViewVM m_MobileDeviceRequestsVM;
private ViewVM m_AuthorizedMobileDevicesVM;
public ViewVM MobileDeviceRequestsVM
{
get { return m_MobileDeviceRequestsVM; }
}
public ViewVM AuthorizedMobileDevicesVM
{
get { return m_AuthorizedMobileDevicesVM; }
}
public MainWindowViewModel()
{
m_MobileDeviceRequestsVM = new ViewVM { ViewDisplay = "MobileDeviceRequests", ViewType = typeof(MobileDeviceRequestsView), ViewModelType = typeof(MobileDeviceRequestsViewModel) };
m_AuthorizedMobileDevicesVM = new ViewVM { ViewDisplay = "AuthorizedMobileDevices", ViewType = typeof(AuthorizedMobileDevicesView), ViewModelType = typeof(AuthorizedMobileDevicesViewModel) };
}
}
The problem I'm facing is how to bind these ViewModel Views in to my grid, tried using a couple of ContentControl however that's not working.
How can I accomplish this?
<Window x:Class="MobileDeviceAuthenticator.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MobileDeviceAuthenticator"
Title="Device Authorization" Height="381" Width="879">
<Grid>
<Grid Margin="0,25,0,0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Content="Authorized Devices" Grid.Row="0" Grid.Column="0" HorizontalAlignment="Left" Margin="20,10,0,0" VerticalAlignment="Top" />
<ContentControl Grid.Row="1" Grid.Column="0" Content="{Binding AuthorizedMobileDevicesVM.View}" />
<Label Content="Device Requests" Grid.Row="0" Grid.Column="1" HorizontalAlignment="Left" Margin="20,10,0,0" VerticalAlignment="Top" />
<ContentControl Grid.Row="1" Grid.Column="1" Content="{Binding MobileDeviceRequestsVM.View}" />
</Grid>
</Grid>
</Window>
I looked at the example's ViewVM class again after I made my comment regarding my reservations about the approach. Ignoring any of that and assuming you have not modified the example's ViewVM code below:
public class ViewVM
{
public string ViewDisplay { get; set; }
public Type ViewType { get; set; }
public Type ViewModelType { get; set; }
public UserControl View { get; set; }
public RelayCommand Navigate { get; set; }
public ViewVM()
{
Navigate = new RelayCommand(NavigateExecute);
}
public void NavigateExecute()
{
if(View == null && ViewType != null)
{
View = (UserControl)Activator.CreateInstance(ViewType);
}
var msg = new NavigateMessage { View = View, ViewModelType = ViewModelType, ViewType = ViewType };
Messenger.Default.Send<NavigateMessage>(msg);
}
}
The issue is that the View property is only assigned to via reflection when NavigateExecute is called. When you bind to AuthorizedMobileDevicesVM.View, it's not instantiated yet. You can move the reflection code into the constructor for your case and it'll work. Of course this means it'll increase memory usage of your application if you're using ViewVM elsewhere for page navigation - looks like it's by design meant to create the view only as necessary.

UI not updating despite ObservableCollection (UWP, XAML) [duplicate]

This question already has answers here:
ObservableCollection not noticing when Item in it changes (even with INotifyPropertyChanged)
(21 answers)
Closed 6 years ago.
I would greatly appreciate some help with this binding issue I'm having. Basically I have a list view showing some information about Files. In the list view item itself, there's some text and also a button.
When this button is clicked I want to disable that button.
Currently I've set up an ObservableCollection - however even though the button click is being registered, the UI doesn't update. If I go to a different screen and return, then the UI updates. So it's not instantaneous.
I think there is some problem with the way RaisePropertyChanged() is working. I know from reading other SO articles that property changes in the object are harder to pick up than say, removing an item or adding an item to the ListView.
I'm completely stuck, any help would be most appreciated. Thanks.
Xaml:
<ListView RelativePanel.Below="heading" ItemsSource="{Binding Pages}" ReorderMode="Enabled" CanReorderItems="True" AllowDrop="True" Margin="0,10" SelectedItem="{Binding Path=SelectedFile,Mode=TwoWay}" >
<ListView.ItemTemplate>
<DataTemplate x:DataType="model:File">
<Grid Padding="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="250"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{x:Bind Path= Name, Mode=TwoWay}" FontWeight="Bold" Padding="0,5" />
<TextBlock Text ="{x:Bind Path}" Grid.Row="1" TextWrapping="Wrap" Padding="10,0,0,0" Foreground="DarkGray" Opacity="0.8" />
<Button Content="X" Grid.Column="1" Grid.RowSpan="2" Command="{x:Bind EnableCommand}" IsEnabled="{x:Bind Path=IsEnabled, Mode=OneWay}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
File.cs:
public class File : ViewModelBase
{
public string Name { get; set; }
public string FileName { get; set; }
public string Path { get; set; }
public string Contents { get; set; }
private Boolean isEnabled = true;
public Boolean IsEnabled {
get { return isEnabled; }
private set {
isEnabled = value;
RaisePropertyChanged("IsChecked");
}
}
private ICommand enableCommand;
public ICommand EnableCommand
{
get
{
if(enableCommand == null)
{
enableCommand = new RelayCommand(() => {
isEnabled = false;
Name += "Disabled";
RaisePropertyChanged();
});
}
return enableCommand;
}
}
}
Viewmodel:
public class MyPageViewModel : BaseViewModel
{
private ObservableCollection<File> pages;
public ObservableCollection<File> Pages
{
get { return pages; }
set
{
pages = value;
RaisePropertyChanged();
}
}
private File selectedFile = new File();
public File SelectedFile
{
get { return selectedFile; }
set
{
Set(ref selectedFile, value);
}
}
public MyPageViewModel()
{
if (ApplicationData.FileList != null)
{
Pages = new ObservableCollection<File>(ApplicationData.FileList);
}
else
{
Pages = new ObservableCollection<File>();
}
}
You notify IsChecked when you should be notifying IsEnabled.
(ObsevarvableCollection only notifies when something is added or removed from it. Changes in the objects it holds are not notified by it.)

DataBinding and iNotifyPropertyChanged in Xamarin

I'm currently making an app using Xamarin Forms. This app will first call a REST service to retrieve the data and display them then store those data into a SQLite Database. I have an update button where if I click on it, it will prompt the REST service once again to retrieve newer data and replace the old data while the app is running. I have tried to implement the INotifyPropertyChanged but the value just wont' change for me. Am I missing anything with my code below? Thanks!
Vitals Object:
public class Vitals
{
public string Height { get; set; }
public string ID { get; set; }
public string Weight { get; set; }
}
Update Method:
async void OnUpdate(object sender, EventArgs e)
{
string tempUser = globalPatient.Username;
string tempPin = globalPatient.PIN;
patUpdate = patientManager.GetPatientByUsername (tempUser, tempPin).Result;
App.PatientDB.DeletePatient(tempID);
App.PatientDB.AddNewPatient (patUpdate, tempPin);
DisplayAlert ("Updated", "Your information has been updated!", "OK");
}
VitalsViewModal:
public class VitalsViewModel: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public VitalsViewModel (Patient patient)
{
vitals = patient.Vitals;
}
private List<Vitals> _vitals;
public List<Vitals> vitals {
get {return _vitals; }
set {
if (_vitals != value) {
_vitals = value;
OnPropertyChanged ("vitals");
}
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}
}
VitalsView
public partial class VitalsView : ContentPage, INotifyPropertyChanged
{
PatientManager patientManager = new PatientManager ();
Patient globalPatient;
public event PropertyChangedEventHandler PropertyChanged;
public VitalsView (Patient patientZero)
{
InitializeComponent ();
BindingContext = new VitalsViewModel (patientZero);
}
}
Xaml
<ListView x:Name="Vitals" ItemsSource="{Binding vitals}" RowHeight="80" BackgroundColor="Transparent">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<StackLayout Orientation="Vertical" Spacing="0" Padding="15">
<Grid>
<Label Font="17" Text="{Binding Height} " FontAttributes="Bold" TextColor="#449BC4" Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="3" />
<Label Font="14" Text="{Binding Weight, StringFormat='Weight: {0}'}" FontAttributes="Bold" Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="3" />
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30" />
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
</Grid>
</StackLayout>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
For vitals to have a change in Xaml, something must replace the whole list, of List<vitals with a new list.
Even though the patient changed and its vitals are new from the update, you have bound to an orphaned patient.vitals whose patient reference is still valid. Hence no change.
You need to specifically change the reference of vitals away from the old one to the new one.
I suggest this:
async void OnUpdate(object sender, EventArgs e)
{
string tempUser = globalPatient.Username;
string tempPin = globalPatient.PIN;
patUpdate = patientManager.GetPatientByUsername (tempUser, tempPin).Result;
App.PatientDB.DeletePatient(tempID);
App.PatientDB.AddNewPatient (patUpdate, tempPin);
MyCurrenViewModel.vitals = patUpdate.Vitals; // Replace old vitals
DisplayAlert ("Updated", "Your information has been updated!", "OK");
}
Note In the above example I would create a property named MyCurrentViewModel on the page, and when assigning the datacontext I would have
public partial class VitalsView : ContentPage, INotifyPropertyChanged
{
VitalsViewModel MyCurrentViewModel { get; set; }
PatientManager patientManager = new PatientManager ();
PatientDemo globalPatient;
public event PropertyChangedEventHandler PropertyChanged;
public VitalsView (Patient patientZero)
{
InitializeComponent ();
//BindingContext = new VitalsViewModel (patientZero);
BindingContext = MyCurrentViewModel = new VitalsViewModel (patientZero);
}
}
Code Review Other Errors
OnUpdate is async which is great, but it never awaits any method call; hence making all calls to it synchronous in nature and blocking the gui thread waiting on results. Never block a gui thread, the app will appear to freeze.
As an option, you can use ObservableCollection instead of List.

How to expose DependencyProperties within 2 ItemsSource lists (doesn't propagate)

Got a StackPanel/ItemsSource within another StackPanel/ItemsSource. The inner one has a Value that updates. Cannot get it to propagate an update to the UI.
Note: As a Registered DependencyProperty - it never updates. As a simple Property {get;set;} it updates ONCE then never again. What should it or they be in order to propagate?
Already checked numerous websites, books, etc - their examples don't demonstrate this use case or work.
What is missing here (just in the propagation)?
NOTE! (if it's not clear) - This is a paired-down fully-running sample to illustrate the problem. Should be clear this isn't even close to prod, let alone int or dev.
(UPDATE: Appears to be a Windows Store app specific issue as it works in std WPF/etc)
SAMPLE DependencyProperty code (.cs):
using System;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using Windows.UI.Core;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace TestDependency
{
public sealed partial class MainPage : Page
{
private TopData topData;
public MainPage()
{
this.InitializeComponent();
this.Do();
}
public async void Do() {
topData = new TopData();
this.TopItemsControl.ItemsSource = topData;
var dispatcher = Dispatcher;
var action = new Action(async () =>
{
while (true)
{
await dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
topData[0][1].Value = topData[0][1].Value + 1;
});
await Task.Delay(1000);
}
});
await Task.Run(action);
}
}
public class TopData : ObservableCollection<MiddleData>
{
public TopData()
{
this.Add(new MiddleData("ABC", new[] {"a1", "a2", "a3"}));
this.Add(new MiddleData("DEF", new[] {"d1", "d2", "d3"}));
this.Add(new MiddleData("GHI", new[] {"g1", "g2", "g3"}));
}
}
public class MiddleData : ObservableCollection<BottomData>
{
public string Name { get; set; }
public MiddleData(string name, string[] list)
{
this.Name = name;
foreach (var item in list)
{
this.Add(new BottomData(item, 0));
}
}
}
public class BottomData : DependencyObject
{
public string Name { get; set; }
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(double), typeof(BottomData), new PropertyMetadata(0d));
public double Value
{
get { return (double)this.GetValue(ValueProperty); }
set { base.SetValue(ValueProperty, value); }
}
public BottomData(string name, double value)
{
this.Name = name;
this.Value = value;
}
}
}
and the SAMPLE xaml code to match:
<Page
x:Class="TestDependency.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:TestDependency"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid x:Name="TopGrid">
<Grid.RowDefinitions>
<RowDefinition Height="5*"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<Grid.Resources>
<DataTemplate x:Key="StackItemTemplate">
<StackPanel x:Name="BottomStackPanel" Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}"/>
<TextBlock Text=": "/>
<TextBlock Text="{Binding Path=Value}" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="SensorDataTemplate">
<StackPanel x:Name="TopStackPanel">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20*"/>
<ColumnDefinition Width="10*"/>
</Grid.ColumnDefinitions>
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Text="{Binding Path=Name}" Grid.Column="0"/>
<StackPanel x:Name="MiddleStackPanel" Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center">
<ItemsControl x:Name="BottomItemsControl" ItemsSource="{Binding}" ItemTemplate="{StaticResource StackItemTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</StackPanel>
</Grid>
</StackPanel>
</DataTemplate>
</Grid.Resources>
<ItemsControl x:Name="TopItemsControl" ItemTemplate="{StaticResource SensorDataTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
</Grid>
</Page>
A couple of highlights:
First, I'm not quite familiar with Windows Phone development: I imported your code into a WPF application. The declaration of xmlns:local as "using:TestDependency" gave me the first punch in the face; I had to replace it with "clr-namespace:TestDependency".
Second, where you had:
<ItemsControl x:Name="BottomItemsControl" ItemsSource="{Binding Path=This}" ...
I changed it into:
<ItemsControl x:Name="BottomItemsControl" ItemsSource="{Binding}"...
Note the removal of the Path=This bit from the binding declaration. This way, everything is working just fine for me: the view is updating sequentially with the incremental values coming from the while loop in the async task in your Do() method.
Give it a try, please.
BottomData needs to implement INotifyPropertyChanged and fire PropertyChanged for Value. This is a difference between WPF (which hooks up DPs more deeply) and other Xaml frameworks such as Windows.UI.Xaml and (I believe) Silverlight.
See Property functionality provided by a dependency property in the Dependency properties overview on MSDN's Windows Dev Center:
Wiring the binding is not the only thing that's needed for most data
binding scenarios. For a one-way or two-way binding to be effective,
the source property must support change notifications that propagate
to the binding system and thus the target. For custom binding sources,
this means that the property must support INotifyPropertyChanged.
This can be done fairly easily in the PropertyChangedCallback hooked up from the DependencyProperty's PropertyMetadata.
public class BottomData : DependencyObject, INotifyPropertyChanged
{
public string Name { get; set; }
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(double), typeof(BottomData), new PropertyMetadata(0d,new PropertyChangedCallback(OnValueChanged)));
public double Value
{
get { return (double)this.GetValue(ValueProperty); }
set { base.SetValue(ValueProperty, value); }
}
public BottomData(string name, double value)
{
this.Name = name;
this.Value = value;
}
private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
BottomData bd = d as BottomData;
bd.NotifyPropertyChanged("Value");
}
void NotifyPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}

Categories