MvvmCross.Forms - Default Spinner will not exit - xamarin.forms

This bug exists in the Sample files (StarWarsSample) and I have been trying to remove it for SEVERAL hours... any advice is appreciated:
Using MvvmCross.Forms
I have an MvxListView which has RefreshCommand bound to a command. I have confirmed it fires. When the MvxListView is pulled down (both iOS and Android) a spinner appears. However it never disappears. I do not have any exceptions being thrown. If I raise a dialog it appears over the spinner, but then once cancelled the spinner remains. My ListView is dynamically updating fine. Just the spinner never disappears... I'm assuming it's coming from some specific function in MVVMCross/Forms but i cannot seem to find any reference to it.
Here's the relevant code (in it's current state after trying different approaches)
<views:MvxListView
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"
SelectionMode="None"
ItemsSource="{mvx:MvxBind BtDevices}"
ItemClick="{mvx:MvxBind BtDeviceSelectedCommand}"
IsPullToRefreshEnabled="True"
RefreshCommand="{mvx:MvxBind RefreshBtDevicesCommand}"
ItemTemplate="{StaticResource DeviceNameTemplate}"
RowHeight="{x:OnPlatform Android=55, iOS=55, UWP=40}"
BackgroundColor="LightBlue"
SeparatorVisibility="None">
</views:MvxListView>
public HomeViewModel(...){
BtDevices = new MvxObservableCollection<BtDevice>();
BtDeviceSelectedCommand = new MvxAsyncCommand<IBtDevice>(BtDeviceSelected);
FetchBtDevicesCommand = new MvxCommand(() =>
{
FetchBtDeviceTask = MvxNotifyTask.Create(LoadDevices, onException: OnException);
RaisePropertyChanged(() => FetchBtDeviceTask);
});
RefreshBtDevicesCommand = new MvxCommand(()=>
{
RefreshDeviceTask = MvxNotifyTask.Create(RefreshBtDevices, OnException);
RaisePropertyChanged(() => RefreshDeviceTask);
});
}
private async Task RefreshBtDevices()
{
IsBusy = true;
var result = await _btService.LoadDevices(_nextPage);
foreach (var d in result.Where(d => BtDevices.FirstOrDefault(i => i.Id == d.Id) == null))
{
BtDevices.Add(d);
}
IsBusy = false;
await RaisePropertyChanged(() => RefreshDeviceTask);
await RaisePropertyChanged(() => RefreshBtDevicesCommand);
}

From the docs you need to bind the IsRefreshing property of the list view.
Gets or sets a value that tells whether the list view is currently refreshing
Something like this:
IsRefreshing="{mvx:MvxBind IsBusy}"
or if you are using an MvxNotifyTask:
IsRefreshing="{mvx:MvxBind RefreshDeviceTask.IsNotCompleted}"
Here you have more informaion, even though it uses directly ListView on the example the same can be applied to MvxListView given that it inherits from ListView.
HIH

Related

MudTable WASM, populating state on page load

Can't find a code sample for this in any of the documentation. Using MudTable ServerData feature, want to store the tables state (page no, page size, sorting) into the url as it changes which Iv'e done:
NavigationManager.NavigateTo($"/list/MyEntity/?PageNo={state.Page}&PageSize={state.PageSize}&SortLabel={state.SortLabel}&SortDirection={(state.SortDirection == MudBlazor.SortDirection.Ascending ? "asc" : "desc")}&Filter={Filter}", false);
However on loading the page, I want to push these parameters into the MudTable
table.CurrentPage = PageNo;
table.RowsPerPage = PageSize;
await table.ReloadServerData();
however I get warnings that I can't use 'table.CurrentPage' outside of component. Any way to implement what I want in current version of MudTable?
Assigning the values to component parameters in code is not the Blazor way. All you need to do is to set them in Razor like this:
<MudTable Items="#Elements" CurrentPage="PageNo" RowsPerPage="PageSize">
...
<PagerContent>
<MudTablePager />
</PagerContent>
</MudTable>
Here is a fiddle to play around with live. It shows that setting the two parameters in razor works. https://try.mudblazor.com/snippet/QaQlvFvLmZkKlaWQ
I think this should do the trick. Sadly, NavigateTo() looks broken.
#inject NavigationManager NavigationManager
<MudTable
#* ... *#
</MudTable>
#code
{
protected override async Task OnAfterRenderAsync(bool firstRender)
{
// on first render, _table is null
if (firstRender)
{
return;
}
var uri = NavigationManager.ToAbsoluteUri(NavigationManager.Uri);
var queryComponents = QueryHelpers.ParseQuery(uri.Query);
queryComponents.TryGetValue("PageNo", out StringValues pageNo);
queryComponents.TryGetValue("PageSize", out StringValues pageSize);
int rowsPerPage = int.Parse(pageSize.First());
int page = int.Parse(pageNo.First());
_table.SetRowsPerPage(rowsPerPage);
//_table.NavigateTo(page); // this is broken
await _table.ReloadServerData();
}
}

Xamarin Form passing parameters between page keep a hard link to it

I try the new Shell of Xamarin Form 4 for a small project.
I have a list of order, then someone chooses an order and start picking some inventory for this order with barcode. To be simple, I use 2 views and 2 viewmodel.
The process is:
1. User select an order the click a button "pickup"
2. I use ViewModelLocator (TinyIoc) to resolve the correct ViewModel of the pickup view
3. I call Initialize on the new ViewModel and pass some parameters needed. Like a list of items needed to be pickup.
4. Open the view in modal state.
I don't understand that if I change some qty in the list I pass on the second viewmodel, then hit the back button (modal view close). The quantity changed is now in the original viewmodel. How this is possible? I was thinking that passing parameter to a function do not share the same variable but just copy it??
Viewmodel of the first view (look at the Initialize function the List passed and the JobEnCours object)
private async Task GoPickup()
{
Device.BeginInvokeOnMainThread(async () =>
{
if (this.CodeJob != null && this.CodeJob != "")
{
this.IsBusy = true;
PrepChariotSP3ViewModel vm = ViewModelLocator.Resolve<PrepChariotSP3ViewModel>();
await vm.InitializeAsync(this._jobEnCours, this.ComposantesPick.ToList()) ;
await Shell.Current.Navigation.PushAsync(new PrepChariotSP3Page(vm));
this.IsBusy = false;
}
});
}
the the Initialize code on the second Viewmodel (look I set the JobEnCours.Description=Test)
public async Task InitialiseAsync(Job jobEnCours, List<ComposantePick> composantePick)
{
Title = "Ceuillette: " + jobEnCours.CodeProd;
this._jobEnCours = jobEnCours;
this.ComposantesPick = new ItemsChangeObservableCollection<ComposantePick>();
foreach(ComposantePick c in composantePick)
{
this.ComposantesPick.Add(c);
}
jobEnCours.Description = "test";
So, If I do the back button, then in the first VM the JobEnCours.Description is now set to "test" how this is possible?!
Any idea?

How to disable WinForms button if SourceList is empty

I've been using ReactiveUI with WinForms and just did the switch to DynamicData, using a SourceList instead of a ReactiveBindingList.
As per this issue, WinForms IBindingList Collection Support was added.
I have a listbox which I bind to a list of strings. In order to make it work with WinForms, I've created a BindingList which is connected to the SourceList:
var Images = new SourceList<string>();
var ImagesBindableWinForms = new BindingList<string>();
Images.Connect().Bind(ImagesBindableWinForms).Subscribe();
The BindingList is then bound to the listbox as follows, which works swell:
d(this.Bind(ViewModel, x => x.AdInfo.ImagesBindableWinForms, x => x.listImages.DataSource));
There is a button to remove items form the list. It should be disabled if the list is empty. Before switching to SourceList, this used to work:
ViewModel.DeleteImageCmd = ReactiveCommand.Create(DeleteImage, ViewModel.CanDeleteImage());
public IObservable<bool> CanDeleteImage()
{
var canDeleteImage = this.WhenAnyValue(vm => vm.AdInfo.Images.Count)
.Select(x => x > 0);
return canDeleteImage;
}
The code would enable or disable the button depending on the list count.
The same code no longer works. I guess no event is fired when the count is updated.
How would I go about disabling the button if the SourceList is empty?
It is necessary to create an ObservableCollectionExtended and bind that to the SourceList as well. The CanDeleteImage should use that instead of the SourceList, or the BindingList:
var ImagesBindable = new ObservableCollectionExtended<string>();
Images.Connect().Bind(ImagesBindable).Subscribe();
public IObservable<bool> CanDeleteImage()
{
var canDeleteImage = this.WhenAnyValue(vm => vm.AdInfo.ImagesBindable.Count)
.Select(x => x > 0);
return canDeleteImage;
}
Only disadvantage is that I now have three lists, but it works.

Activity Indicator is not visible on xaml page in xamarin.forms?

I have an activity indicator on xaml page. Initially its IsVisible property is false. I have a button on page. When user click on button it calls a web service to get data. I change the value of IsVisible property to true before calling the service so that activity indicator starts to display on page and after successful calling of service I change its value to again false so that it doesn't show any more on page.
But it is not working. I know the actual problem. When we call the web service the UI thread gets block and it doesn't show the activity indicator.
How I can enable the UI thread when web service gets called so that activity indicator can show on page until we get the data?
Try making your webservice call into an async and await it.
Depending on how you've structured things you may have to use a TaskCompletionSource as the following example demonstrates.
In this example when the button is clicked, the button is made invisible, and the ActivityIndicator is set to IsRunning=True to show it.
It then executes your long running task / webservice in the function ExecuteSomeLongTask using a TaskCompletionSource.
The reason for this is that in our button click code, we have the final lines:-
objActivityIndicator1.IsRunning = false;
objButton1.IsVisible = true;
That stop the ActivityIndicator from running and showing, and also set the button back to a visible state.
If we did not use a TaskCompletionSource these lines would execute immediately after calling the ExecuteSomeLongTask if it was a normal async method / function, and would result in the ActivityIndicator not running and the button still being visible.
Example:-
Grid objGrid = new Grid()
{
};
ActivityIndicator objActivityIndicator1 = new ActivityIndicator();
objGrid.Children.Add(objActivityIndicator1);
Button objButton1 = new Button();
objButton1.Text = "Execute webservice call.";
objButton1.Clicked += (async (o2, e2) =>
{
objButton1.IsVisible = false;
objActivityIndicator1.IsRunning = true;
//
bool blnResult = await ExecuteSomeLongTask();
//
objActivityIndicator1.IsRunning = false;
objButton1.IsVisible = true;
});
objGrid.Children.Add(objButton1);
return objGrid;
Supporting function:-
private Task<bool> ExecuteSomeLongTask()
{
TaskCompletionSource<bool> objTaskCompletionSource1 = new TaskCompletionSource<bool>();
//
Xamarin.Forms.Device.StartTimer(new TimeSpan(0, 0, 5), new Func<bool>(() =>
{
objTaskCompletionSource1.SetResult(true);
//
return false;
}));
//
return objTaskCompletionSource1.Task;
}
You need to do your work in an asynchronous way. Or in other words: Use Asnyc & Await to ensure, that you UI works well during the call.
You can find more informations in the Xamarin Docs.
async and await are new C# language features that work in conjunction
with the Task Parallel Library to make it easy to write threaded code
to perform long-running tasks without blocking the main thread of your
application.
If you need further asistance, please update your question and post your code or what you have tried so far.

Flex Advanced Data Grid w/ hierarchical data: How to access currentTarget fields on dragdrop event?

So this is driving me crazy. I've got an advanced data grid with a dataprovider that's an array collection w/ hierarchical data. Each object (including the children) has an id field. I'm trying to drag and drop data from within the ADG. When this happens, I need to grab the id off the drop target and change the dragged object's parentid field. Here's what I've got:
public function topAccountsGrid_dragDropHandler(event:DragEvent):void{
//In this function, you need to make the move, update the field in salesforce, and refresh the salesforce data...
if(checkActivateAccountManageMode.selected == true) {
var dragObj:Array = event.dragSource.dataForFormat("treeDataGridItems") as Array;
var newParentId:String = event.currentTarget['Id'];
dragObj[0].ParentId = newParentId;
} else {
return;
}
app.wrapper.save(dragObj[0],
new mx.rpc.Responder(
function():void {
refreshData();
},
function():void{_status = "apex error!";}
)
);
}
I can access the data I'm draggin (hence changing parentId) but not the currentTarget. I think the hierarchical data is part of the problem, but I can't find much in the documentation? Any thoughts?
event.currentTarget is not a node, it's the ADG itself. However, it's quite easy to get the information you want, since the ADG stores that data internally (as mx_internal).
I'm using the following code snippets (tested with Flex SDK 4.1) in a dragOver handler, but I guess it will work in a dragDrop handler too.
protected function myGrid_dragDropHandler(event:DragEvent):void
{
// Get the dragged items. This could either be an Array, a Vector or NULL.
var draggedItems:Object = getDraggedItems(event.dragSource);
if (!draggedItems)
return;
// That's our ADG where the event handler is registered.
var dropTarget:AdvancedDataGrid = AdvancedDataGrid(event.currentTarget);
// Get the internal information about the dropTarget from the ADG.
var dropData:Object = mx_internal::dropTarget._dropData;
// In case the dataProvider is hierarchical, get the internal hierarchicalData aka rootModel.
var hierarchicalData:IHierarchicalData = dropTarget.mx_internal::_rootModel;
var targetParent:Object = null;
// If it's a hierarchical data structure and the dropData could be retrieved
// then get the parent node to which the draggedItems are going to be added.
if (hierarchicalData && dropData)
targetParent = dropData.parent;
for each (var draggedItem:Object in draggedItems)
{
// do something with the draggedItem
}
}
protected function getDraggedItems(dragSource:DragSource):Object
{
if (dragSource.hasFormat("treeDataGridItems"))
return dragSource.dataForFormat("treeDataGridItems") as Array;
if (dragSource.hasFormat("items"))
return dragSource.dataForFormat("items") as Array;
if (dragSource.hasFormat("itemsByIndex"))
return dragSource.dataForFormat("itemsByIndex") as Vector.<Object>;
return null;
}
var dropData:Object = mx_internal::dropTarget._dropData;
should be
var dropData:Object = dropTarget.mx_internal::_dropData;
Try this.

Resources