Blazor InputSelect OnChange trigger UI Update - c#

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.

Related

Reset input field value if value is invalid in Blazor

I have some text fields binding inside a for loop.
<Input type="text" #onchange='(ChangeEventArgs e)=>DataChange(e,item)' value="#item.value" />
here if user type anything to the input fields I have a custom validation inside DataChange function.
private void DataChange(ChangeEventArgs e, PnL pnl)
{
if(e.Value.ToString().Contains(10))
{
pnl.value = e.Value.ToString();
}
}
if change is valid then only Iam assigning value to the item in the list.
But if its invalid i need to reset the value to original.
I have tried calling StateHasChange() but that didn't helps.
Can anybody helps?
Try this:
private async Task DataChange(ChangeEventArgs e, PnL pnl)
{
if(e.Value.ToString().Contains(10))
{
pnl.value = e.Value.ToString();
}
else
{
var tempValue = pnl.value;
pnl.value = string.Empty();
await Task.Yield();
pnl.value = tempValue;
}
}
So what happens here is this.
The code runs synchronously to the await Task.Yield.
At this point pnl.value is a blank string.
DataChange yields execution to the Blazor event handler which loads a render action onto the Renderer Queue.
The Yield allows the Renderer to service it's render queue, and re-render the component. pnl.value is blank - different from the last render - so the input gets updated in the DOM.
The code after the yield now runs to completion, setting pnl.value back to it's original value.
DataChange completes and the Blazor event handler loads a second render action onto the Renderer Queue which now re-renders the input as the value has changed again.
In your code everything runs synchronously. You can call StateHasChanged as many times as you like, the Renderer only gets task time to run it's queue after DataChange completes. At this point the Renderer still thinks the input is set to the original value of pnl.value (not the edited value), so the DOM doesn't get updated.
There's an article on CodeProject with more detail - https://www.codeproject.com/Articles/5310624/Blazor-UI-Events-and-Rendering
When the #onchange is happening, the binding is already over, so the invalid string is at that time in pnl.value.
One of the solution could be an extra property for binding. The value property would only change when the tempVal is valid, otherwise it would reset the tempVal:
<input type="text" #bind-value="item.tempVal" />
#code {
PnL item = new() { value = "hello10hi", tempVal = "hello10hi" };//valid values
class PnL
{
public string value { get; set; }
string _tempVal;
public string tempVal { get => _tempVal; set { _tempVal = value; DataChange(value, this); } }
void DataChange(string newVal, PnL pnl)
{
if (newVal.Contains("10"))//is valid
{
pnl.value = newVal;
}
else
{
pnl.tempVal = pnl.value;
}
}
}
}
EDIT
(The code has been updated).
The problem was, that the tempVal was not updating when writing, so it still had the default value, so no change after assigning the value, thus no update in UI.
With bind-value the tempVal will be always updated. The "problem" there is that we loose the onechange code (because we use bind). Thus I had to interact with changes inside the tempVal property.

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

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!

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.

Programatically change display of the sort icon in datagrid column header

I asked this question previously and did not get an answer but now I have more detail.
Basically I want to programatically display the column sort icon in a wpf datagrid column.
I have the following code to do this:
private void dtgMain_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
dtgMain.Columns[0].SortDirection = ListSortDirection.Ascending;
}
This seems to set the sort order of the column but when the grid is drawn the icon does not show.
When I add a message box into the method it works fine. My question is twofold. Why would the message box cause the method to work? And how can I get it to work without the use of a messagebox?
This is the method working with the messagebox in it:
private void dtgMain_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
MessageBox.Show("Here");
dtgMain.Columns[0].SortDirection = ListSortDirection.Ascending;
}
edit
Here is the method that is setting the datacontext of the datagrid
public void processLoad(string response)
{
XmlDataProvider provider = new XmlDataProvider();
if (provider != null)
{
System.Xml.XmlDocument doc = new System.Xml.XmlDocument();
doc.LoadXml(response);
provider.Document = doc;
provider.XPath = "/moo/response/data/load/panel";
dtgMain.DataContext = provider;
}
}
Please let me know if you need anymore information.
OK, I suspect what is happening is that the data layout changes caused by the DataContext update are being completed after your call to set the direction arrow, and it is therefore being erased after you set it. Interestingly, in my case it failed to work even when I put the messagebox in, perhaps because that was hanging up the UI thread while it displayed.
Could you try replacing the line that sets the sort direction with a similar call put on the dispatcher queue:
dtgMain.Dispatcher.BeginInvoke(new Action(() =>
{
dtgMain.Columns[0].SortDirection = ListSortDirection.Ascending;
}), DispatcherPriority.ApplicationIdle);
and see if that works?

Categories

Resources