How to Refresh UI from non-UI thread in Windows 10 - c#

I'm working in an application that takes its own content from a server. Each X seconds has to check if there have been changes and, if so, to refresh the related screens.
I'm trying to refresh the UI from the thread that checks if any changes have been uploaded, but the thing is that the code is executed but the view it's not
refreshed.
Here's a sample of the code:
//Class that checks changes in server
await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal,
() =>
{
//Page that has to refresh its UI
Home h = new Home();
h.RefreshData(update);
});
break;
//HOME Page
public void RefreshData(PushElement update)
{
_source.Clear(); //ObservableCollection<UserControl>, is the ItemsSource of the Main ListView of the Home page
NotifyPropertyChanged();
setContentFromProps("URL_HOME"); //Function that fills back the ListView
}
//Property bound to ItemsSource in the XAML
public ObservableCollection<UserControl> Source
{
get
{
return _source;
}
set
{
_source = value;
NotifyPropertyChanged();
}
}
If I put a breakpoint, the code is executed but the UI it's never refreshed. I have tried many ways to do it, but unsuccessfully...
How could I achieve it?
Thanks in advance!

Related

Blazor InputSelect OnChange trigger UI Update

I am not quite sure if I am asking the right question. I assume other people have had this issue.
I built my own Blazor Grid component. I am using an bound to a property.
I have a function to load my grid. I changed my bound property to a full getter,setter. In the setter, I call my function to load the grid. This works fast and easy in pretty much all instances. But, I have one grid that when binding it will take a few extra seconds to complete.
The problem: I can't seem to figure out how to get my waiting spinner component to show when loading my grid.
Example Blazor Markup:
#if (dataGrid == null)
{
<hr />
<BitcoSpinner></BitcoSpinner>
}
else
{
<BitcoGrid TheGrid="dataGrid"></BitcoGrid>
}
Here is my property and GridLoading:
private string selectedGroup1 = "";
public string selectedGroup
{
get => selectedGroup1;
set
{
selectedGroup1 = value;
LoadGrid();
}
}
private void LoadGrid()
{
dataGrid = null;
PT_Grid_Admin ptGrid = new PT_Grid_Admin(permitTraxLibrary, gridParams);
dataGrid = ptGrid.ADMIN_FeeList(feeList.Fee_Key, selectedGroup);
}
You should define LoadGrid method asynchronously. Therefore, at the beginning of the program, when the data grid value is set, your spinner will be displayed until the data grid value is not received. Then, after receiving the data grid value, the else part of the condition will be executed and its value will be displayed to the user.
It may not take much time to receive information from the DB in local mode, so the following code can be used to simulate the delay:
System.Threading.Thread.Sleep(5000);
In general, I think that if your code changes like this, you can see the spinner.
private string selectedGroup1 = "";
public string selectedGroup
{
get => selectedGroup1;
set
{
selectedGroup1 = value;
LoadGrid();
}
}
private async Task LoadGrid()
{
dataGrid = null;
System.Threading.Thread.Sleep(5000);
.
.
}
Of course, it is better to load the datagrid in OnInitializedAsync method. For more info you can refer to this link.

Xamarin Picker showing undesirable elements on UI

Pulling my hair out at the point. My Picker is showing an annoying line on the UI and/or the Picker's Title property if that's enabled. I simply want the Picker, not the stuff showing on the UI beneath it. Any idea on how to achieve this? Do I have to use a custom renderer or is there something simple I'm missing?
Note: The list is intentionally empty in the below examples.
Without the title, I click the Existing button, the line shows, click it again and the modal appears:
With the title, I click the Existing button, the line and title show, click it again and the modal appears:
Don't know why I have to click the button twice. But it's only on the initial page load. If I exit the modal and click the button again, it immediately appears, no double-click. Not sure if that's related to my original question, but thought I'd include it for additional information.
NewSubjectPage.xaml (chopped for brevity)
<ContentPage.Content>
<StackLayout x:Name="NewSubjectMainLay">
<ScrollView>
<StackLayout x:Name="NewSubjectChildLay">
<Grid>
<Button
x:Name="NewSubjectExisChrtBtn"
Clicked="NewSubjectExisChrtBtn_Clicked"
Grid.Column="2"
Text="Existing" />
</Grid>
</StackLayout>
</ScrollView>
<Picker
x:Name="NewSubjectExisChrtPck"
IsVisible="False"
ItemsSource="{Binding Charts}"
ItemDisplayBinding="{Binding Name}"
SelectedIndexChanged="NewSubjectExisChrtPck_SelectedIndexChanged"
Title="Select chart"
Unfocused="NewSubjectExisChrtPck_Unfocused"/>
</StackLayout>
</ContentPage.Content>
NewSubjectPage.xaml.cs (chopped for brevity)
public partial class NewSubjectPage : ContentPage
{
private string chartName;
private readonly NewSubjectViewModel _viewModel;
public string ChartName
{
get => chartName;
private set
{
chartName = value;
OnPropertyChanged();
}
}
public NewSubjectPage()
{
InitializeComponent();
BindingContext = _viewModel = new NewSubjectViewModel();
chartName = "";
}
private void NewSubjectExisChrtBtn_Clicked(object sender, EventArgs e)
{
_viewModel.LoadChartsCommand.Execute(null);
NewSubjectExisChrtPck.IsVisible = true;
NewSubjectExisChrtPck.Focus();
}
private void NewSubjectExisChrtPck_SelectedIndexChanged(object sender, EventArgs e)
{
var picker = (Picker)sender;
int selectedIndex = picker.SelectedIndex;
if (selectedIndex != -1)
{
ChartName = picker.Items[picker.SelectedIndex];
}
}
private void NewSubjectExisChrtPck_Unfocused(object sender, FocusEventArgs e)
{
NewSubjectExisChrtPck.IsVisible = false;
NewSubjectExisChrtPck.Unfocus();
}
}
NewSubjectViewModel.cs (chopped for brevity)
class NewSubjectViewModel : BaseViewModel
{
private ObservableCollection<Chart> charts;
public ObservableCollection<Chart> Charts
{
get { return charts; }
private set
{
charts = value;
OnPropertyChanged();
}
}
public Command LoadChartsCommand { get; set; }
public NewSubjectViewModel()
{
LoadChartsCommand =
new Command(
async () => await ExecuteLoadChartsCommand()
);
}
private async Task ExecuteLoadChartsCommand()
{
try
{
IndicatorRunning = true;
var list = await App.Database.GetChartsAsync();
Charts = new ObservableCollection<Chart>(list);
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
}
}
Thanks for your help! Let me know if you need to see anything else.
First, I was not able to reproduce the issue of the modal not showing until a second click of the button. You might need to provide more code for that to happen. To even use your code sample I had to replace var list = await App.Database.GetChartsAsync(); with something else to simulate a long running task that returns an empty list. Also had to create a Chart type with a Name property. Not to mention BaseViewModel. In the future, please provide all code to reproduce the issue so there is minimal work required of the person who is trying to help you. There is concept on Stack Overflow called the MCVE (minimal, complete, verifiable example): http://stackoverflow.com/help/mcve
That said, perhaps the first click is actually focusing the emulator and making it the active app, and then the second is the first actual click on the button? This I can reproduce. IOW, if the emulator is not the foreground app, you have to click it once to make it active and then your app will handle clicks.
As for the undesirable UI, you do realize that the Picker UI is basically a clickable label that when clicked displays the actual picker modal? So when you make it visible, what you are making visible is the label UI, which has the line and the Title (if set), and when you focus that label, then the actual picker dialog is displayed. If you don't want to see the UI Label at all, then why make it visible? You can focus it without making it visible, so just remove the line NewSubjectExisChrtPck.IsVisible = true;
As a side note, when you call _viewModel.LoadChartsCommand.Execute(null); that calls an async method, var list = await App.Database.GetChartsAsync(); , so the LoadChartsCommand returns before you set the Charts property, and also then the code following the call to _viewModel.LoadChartsCommand.Execute(null); also executes before LoadChartsCommand really finishes, so you are making the picker visible and focusing it before the LoadChartsCommand finishes as well, so if you were loading actual items for the picker to display, they may not be there the first time. Maybe it's just the sample code, but I see no reason to use a command here, but rather you should just call an awaitable task. You are not binding to the LoadChartsCommand, so I see no reason for you to even use a Command in this scenario. Instead I suggest making ExecuteLoadChartsCommand public and calling it directly, e.g.:
private async void NewSubjectExisChrtBtn_Clicked(object sender, EventArgs e)
{
//_viewModel.LoadChartsCommand.Execute(null); // Returns immediately, so picker not loaded with items yet.
await _viewModel.ExecuteLoadChartsCommand(); // Waits for method to finish before before presenting the picker.
//NewSubjectExisChrtPck.IsVisible = true;
NewSubjectExisChrtPck.Focus();
}

Prism SetProptery not firing Notification?

So having a play with PRISM and I have a grid who's Visibility property is bound to a property in a view model like so.
Xaml
Grid Grid.RowSpan="2" Grid.ColumnSpan="3" Background="#7F808080" Visibility="{Binding LoadingVisibility}">
Code Behind
private Visibility loadingVisibility = Visibility.Visible;
public Visibility LoadingVisibility
{
get
{
return loadingVisibility;
}
set
{
SetProperty(ref loadingVisibility, value);
}
}
Now if I do this LoadingVisibility = Visibility.Collapsed;, the grid does not disappear and is still visible.
If I then set a breakpoint at SetProperty(ref loadingVisibility, value); I can see the original value of loadingVisibility, which is set as Visibile, and I can see that value is set to Collapsed.
If I then step on I can see loadingVisiblity has now changed to collapsed as it should. At this point I expect the Grid to be notified which in turn executes 'Get' to retrieve the value. This does not occur.
The binding is working because the Get is called when loading up and if I change private Visibility loadingVisibility = Visibility.Visible; to Collapsed and run the code the grid starts invisible.
So my question is, after SetProperty is executed, why is the Get not?
EDIT:
Just so you can see where I set the property.
public ShellViewModel(IEventAggregator IEventAggregator)
{
IEventAggregator.GetEvent<PubSubEvent<HardwareLoaded>>().Subscribe(x =>
{
if (!x.HardwareOK)
{
MessageBox.Show("There was an issue loading hardware. See Log");
}
LoadingVisibility = Visibility.Collapsed;
});
}
EDIT 2:
Just found something interesting, if I comment out LoadingVisibility = Visibility.Collapsed; in the Subscribe and then add a button to the xaml and have the click event like so then everything works fine.
private void Button_Click(object sender, RoutedEventArgs e)
{
mvm.LoadingVisibility = Visibility.Collapsed;
}
So now I guess the question is why, when both methods call the set property, does only one fully work and cause the Get to work?
Quite often when using the EventAggregator, you're working on the UI thread. But seeing the HardwareLoaded type it came to me that you might be doing some checking on another thread. And as we know, bindings have to be updated from the UI thread. Normally you would use Dispather.BeginInvoke, but Prism's EventAggregator has an overload in the Subscribe method to tell the handler to offload to the UI thread.
IEventAggregator.GetEvent<PubSubEvent<HardwareLoaded>>().Subscribe(x =>
{
if (!x.HardwareOK)
{
MessageBox.Show("There was an issue loading hardware. See Log");
}
LoadingVisibility = Visibility.Collapsed;
}, ThreadOption.UIThread);

WPF ListView: Changing ItemsSource does not change ListView

I am using a ListView control to display some lines of data. There is a background task which receives external updates to the content of the list. The newly received data may contain less, more or the same number of items and also the items itself may have changed.
The ListView.ItemsSource is bound to an OberservableCollection (_itemList) so that changes to _itemList should be visible also in the ListView.
_itemList = new ObservableCollection<PmemCombItem>();
_itemList.CollectionChanged += new NotifyCollectionChangedEventHandler(OnCollectionChanged);
L_PmemCombList.ItemsSource = _itemList;
In order to avoid refreshing the complete ListView I do a simple comparison of the newly retrieved list with the current _itemList, change items which are not the same and add/remove items if necessary. The collection "newList" contains newly created objects, so replacing an item in _itemList is correctly sending a "Refresh" notification (which I can log by using the event handler OnCollectionChanged of the ObservableCollection`)
Action action = () =>
{
for (int i = 0; i < newList.Count; i++)
{
// item exists in old list -> replace if changed
if (i < _itemList.Count)
{
if (!_itemList[i].SameDataAs(newList[i]))
_itemList[i] = newList[i];
}
// new list contains more items -> add items
else
_itemList.Add(newList[i]);
}
// new list contains less items -> remove items
for (int i = _itemList.Count - 1; i >= newList.Count; i--)
_itemList.RemoveAt(i);
};
Dispatcher.BeginInvoke(DispatcherPriority.Background, action);
My problem is that if many items are changed in this loop, the ListView is NOT refreshing and the data on screen stay as they are...and this I don't understand.
Even a simpler version like this (exchanging ALL elements)
List<PmemCombItem> newList = new List<PmemCombItem>();
foreach (PmemViewItem comb in combList)
newList.Add(new PmemCombItem(comb));
if (_itemList.Count == newList.Count)
for (int i = 0; i < newList.Count; i++)
_itemList[i] = newList[i];
else
{
_itemList.Clear();
foreach (PmemCombItem item in newList)
_itemList.Add(item);
}
is not working properly
Any clue on this?
UPDATE
If I call the following code manually after updating all elements, everything works fine
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
But of course this causes the UI to update everything which I still want to avoid.
After a change, you can use the following to refresh the Listview, it's more easy
listView.Items.Refresh();
This is what I had to do to get it to work.
MyListView.ItemsSource = null;
MyListView.ItemsSource = MyDataSource;
I know that's an old question, but I just stumbled upon this issue. I didn't really want to use the null assignation trick or the refresh for just a field that was updated.
So, after looking at MSDN, I found this article:
https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.inotifypropertychanged?redirectedfrom=MSDN&view=netframework-4.7.2
To summarize, you just need the item to implement this interface and it will automatically detect that this object can be observed.
public class MyItem : INotifyPropertyChanged
{
private string status;
public string Status
{
get => status;
set
{
OnPropertyChanged(nameof(Status));
status = value;
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
So, the event will be called everytime someone changes the Status. And, in your case, the listview will add a handler automatically on the PropertyChanged event.
This doesn't really handle the issue in your case (add/remove).
But for that, I would suggest that you have a look at BindingList<T>
https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.bindinglist-1?view=netframework-4.7.2
Using the same pattern, your listview will be updated properly without using any tricks.
You should not reset ItemsSource of ListView each time observable collection changed. Just set proper binding that will do your trick. In xaml:
<ListView ItemsSource='{Binding ItemsCollection}'
...
</ListView>
And in code-behind (suggest to use MVVM) property that will be responsible for holding _itemList:
public ObservableCollection<PmemCombItem> ItemsCollection
{
get
{
if (_itemList == null)
{
_itemList = new ObservableCollection<PmemCombItem>();
}
return _itemList;
}
}
UPDATE:
There is similar post which most probably will Answer your question: How do I update an ObservableCollection via a worker thread?
I found a way to do it. It is not really that great but it works.
YourList.ItemsSource = null;
// Update the List containing your elements (lets call it x)
YourList.ItemsSource = x;
this should refresh your ListView (it works for my UAP :) )
An alternative on Xopher's answer.
MyListView.ItemsSource = MyDataSource.ToList();
This refreshes the Listview because it's a other list.
Please check this answer:
Passing ListView Items to Commands using Prism Library
List view Items needs to notify about changes (done is setter)
public ObservableCollection<Model.Step> Steps
{
get { return _steps; }
set { SetProperty(ref _steps, value); }
}
and UpdateSourceTrigger need to be set in xaml
<Image Source="{Binding ImageData, UpdateSourceTrigger=PropertyChanged}" />

Databinding updating with outdated values

I am not understanding the following 4 points about databinding. Not sure if i get unexpected results (outdated data in code behind), or if this is just a result of me misunderstanding how things work.
Bound data to a textbox updates when i leave the textbox. What event
EXACTLY does this? LostFocus?
When using the now changed data in code behind
it seems to still use the OLD data. Why is this happening? Could
point 3 be the reason?
After the textbox updates i did a test and set datacontext
to nothing and reaplied datacontext to the same scource. The values
shown are the values before i edited them. Why did they show up after editing, but
returned to the old values after rebinding?
After changing the values for the second time it seems
like code behind uses the data after my first change. Rebinding like
in point 3 leads to the same result (value after first change,
second change ignored). Seems like code behind is always one update behind, can i change this?
Anyone able to explain why this happens?
Desired behavior:
I want the people count to update when I edit the housing count. Preferable on the fly, but after losing focus is fine. When losing focus the value for isle ID 0 should be the right one tho, and not the outdated value.
For easier understanding, a picture with 3 screens and related code samples.
http://www.mathematik-lehramtsstudium.de/BindingExample.jpg
My class:
//class for isles
public class isle : INotifyPropertyChanged
{
//Dummyvariables,...
private int _intBauer;
private int _intBauerBev;
//variables
public int intIsleID { set; get; } //isle ID
public string strName { set; get; } //isle name
public int intBauer //housing count
{
set
{
this._intBauer = value;
NotifyPropertyChanged("intBauer"); NotifyPropertyChanged("intBauerBev");
}
get
{
return _intBauer;
}
}
public int intBauerBev //each house hosts 8 people
{
set { this._intBauerBev = value;}
get { return intBauer * 8; }
}
protected void NotifyPropertyChanged(String propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
LostFocus-Event for updating the right side oft he page
private void textboxHäuser_LostFocus(object sender, RoutedEventArgs e)
{
//Gesamtzahl neu berechnen
listIsles[0].intBauer = 0;
for (int i = 1; i < 10; i++)
{
listIsles[0].intBauer += listIsles[i].intBauer;
}
//hard refresh DataContext, since it does not seem to update itself
//leaving these two lines out of my code changes nothing tho, as expected
gridInfoGesamt.DataContext = "";
gridInfoGesamt.DataContext = listIsles[0];
}
The issue i was facing is the order in which events get fired in this case. Or more accurate: Two things happening at once.
TextBox uses the "LostFocus"-Event to update the property, same as the event i used to update my other TextBox controls. Since both fired at once i used the "outdated" data for calculations, therefore it looked like my UI lagged one step behind on one side.
To fix this i simply had to change the way my TextBox updates the property, by doing my binding like this in XAML:
Text="{Binding intBauer, UpdateSourceTrigger=PropertyChanged}"
Now the property is updated instantly, before "LostFocus" and even before "TextChanged".
This also opens the possibility to update the UI as the user changes values and not only after he is finished. Much cleaner and better looking.

Categories

Resources