Can we add Pins not in collection to Map.Pins? - xamarin.forms

I have a Xamarin project in which I'm using Xamarin.Essentials' Xamarin.Maps. I have an IObservableCollection loaded with entities which I've bound to my Map in my XAML view/page, but I would also like to add a Pin that's not in that collection to that Map manually. Can it be done or should I just forego the XAML bind and deal with Map.Pins manually?
MyPage.xaml
...
<maps:Map x:Name="MyMap" FlexLayout.Grow="1" ItemsSource="{Binding Churrerxs}">
<maps:Map.ItemTemplate>
<DataTemplate>
<maps:Pin x:DataType="model:MyModel" Position="{Binding PositionAttr}" Label="{Binding NameAttr}" />
</DataTemplate>
</maps:Map.ItemTemplate>
</maps:Map>
...
I've tried adding the following within MyMap's definition, but obviously it doesn't work:
<maps:Map.Pins>
<maps:Pin Position="{Binding MyPosition}" Label="MyPin" />
</maps:Map.Pins>
I've also tried creating a Pin on my page's constructor and adding a handler that's executed when MyPosition changes like so:
if(_viewModel.MyPosition != null)
{
MyPin.Position = _viewModel.MyPosition;
if (!MyMap.Pins.Contains(MyPin))
{
MyMap.Pins.Add(MyPin);
}
}
But since Pins is already being manipulated by the ItemTemplate in XAML, I'm already falling into race conditions where I add the pin manually but soon enough it gets removed.
So, I'm trying to figure out how to best handle this. I wish Map could have more than a single array of Pins, so we could use bindings for different collections, but that doesn't seem to be the case. I'm thinking a) I should handle the collection manually, or b) instead of a Pin I should display MyPosition by drawing a circle or some other graphic that's independent from the Pins array. But I'm open to suggestions if anyone knows of a proper pattern to handle this type of scenario.

Indeed, rather than trying to manually shoehorn a Pin to a collection that's handled by a binding, or handling the binding manually in order to fit my "special pin" into the collection, the simplest solution is to have a readonly Circle MyCircle defined in your view/page, populate it with a new Circle { Center = new Position(), Radius = Distance.FromMeters(10) } in it's constructor and then simply updating it's position whenever it changes (note: I only add it to the map after a position has actually been acquired, otherwise it'd be impossible to draw).
if(_viewModel.MyPosition != null)
{
MyCircle.Center = _viewModel.MyPosition;
if (!MyMap.MapElements.Contains(MyCircle))
{
MyMap.MapElements.Add(MyCircle);
}
}
It's left as an exercise to the reader to figure out how to handle input if need be (I don't need to interact with the circle, just display it).

Related

How to call async function from property setter?

I have a wpf application with a TextBox bound to ActualPageNumber property in the VM. I also have a DataGrid bound to an ObservableCollection which displays the given page. The data are stored in DB. When I change the ActualPageNumber, the setter accesses the db which can be slow. That is why I wanted an async setter, to keep the gui responsive.
I understand there is no async setter: https://blog.stephencleary.com/2013/01/async-oop-3-properties.html
I also found useful stuff like https://stackoverflow.com/a/9343733/5852947, https://stackoverflow.com/a/13735418/5852947, https://nmilcoff.com/2017/07/10/stop-toggling-isbusy-with-notifytask/
Still I struggle how to go on this case. AsyncEx library can be the solution, an example would be nice.
I just would like to notify the user that the page is actually loading. If I could call async from the setter I could do it, but then I still can not use await in the setter because it is not async.
I also have a DataGrid bound to an ObservableCollection which displays the given page.
This is going to be the difficult part. DataGrid (and DataTable and friends) are designed with a synchronous API, and have never been updated to support asynchrony.
I'm not terribly familiar with DataGrid, but I'd say your options are:
Replace the DataGrid with your own custom control - say, a ListView that displays custom controls. Then you can display a loading spinner since you control the custom control. There are some common patterns for this like NotifyTask.
There might be a way to virtualize the data in the DataGrid in a way that it would asynchronously load. I'm not familiar enough with DataGrid to say whether this is actually possible, but it's worth looking into.
1) For the responsiveness of the DataGrid, this binding property might help: IsAsync=True
<DataGrid ItemsSource="{Binding MyCollection, IsAsync=True}"
also look into these DataGrid properties:
VirtualizingPanel.IsVirtualizing
VirtualizingPanel.VirtualizationMode (you'll probably need Recycling)
VirtualizingPanel.IsVirtualizingWhenGrouping
EnableRowVirtualization
EnableColumnVirtualization
But be careful, virtualization can play tricks on you. For example, I had a RowHeader (with the row number) and the values got scrambled when virtualization was on.
2) About the async setter for data binding: I was using a custom version of IAsyncCommand (see Stephen Cleary's example).
I used the command in 2 ways: a) binding to it from the view (avoiding the async setter altogether) or b) launching it from the setter (not nice).
Example: I created an UpdateCommand as an AsyncCommand and placed everything I needed done asynchronously (like getting the values from the DB). Everything in this command is wrapped within a display+hide of a "in progress"-like control - in my case, a transparent cover with a spinner + "please wait...", to prevent other user actions (the "screen" is visible while the task is performed). Stripped down sample:
....
public MainWindowViewModel()
{
UpdateCommand = AsyncCommand.Create(Update); // our own custom implementation of AsyncCommand
}
....
public AsyncCommand UpdateCommand { get; }
internal async Task Update(object arg)
{
await SafeWrapWithWaitingScreenAsync(async () =>
{
var value = (int)arg; // or the ActualPageNumber, if used from a1)
var data = await GetDataFromDb(value).ConfigureAwait(false);
...// fill in MyCollection (which is the DataGrid's ItemsSource) using the data
OnPropertyChanged(nameof(MyCollection));// if still needed
}).ConfigureAwait(false);
}
....
public async Task SafeWrapWithWaitingScreenAsync(Func<Task> action)
{
DisplayWaitingScreen = true; //Visibility of the "Waiting screen" binds to this
try
{
await action().ConfigureAwait(false);
}
catch (Exception ex)
{
HandleException(ex); // display/log ex
}
finally
{
DisplayWaitingScreen = false;
}
}
a) Binding to the command from the view and
a1) in the command's body use ActualPageNumber property instead of the arg value
or a2) passing a CommandParameter which binds to the same property as TextBox.Text does. Example (could be missing something, couse is not the real code):
<TextBox Text="{Binding ActualPageNumber, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<TextBox.InputBindings>
<KeyBinding Key="Return" Command="{Binding UpdateCommand}" CommandParameter="{Binding ActualPageNumber}" />
<KeyBinding Key="Enter" Command="{Binding UpdateCommand}" CommandParameter="{Binding ActualPageNumber}" />
</TextBox.InputBindings>
</TextBox>
b) Not sure this is right, but before seeing Stephen's approach with NotifyTaskCompletion<TResult> (which I will probably use in the future), for the setter, I launched the command something like:
private int actualPageNumber;
public int ActualPageNumber
{
get => actualPageNumber;
set
{
actualPageNumber = value;
OnPropertyChanged(); //the sync way
UpdateCommand.Execute(value);
}
}

Change width of a lookup column

I've created a lookup with two columns, first one containing and integer which works just fine but the second one has a long name and this is where the problem arises. Users should horizontally scroll in order to check the entire string and even in that case, the column's width is not big enough to display the whole data.
I've found this :
Adjusting column width on form control lookup
But i don't understand exactly where and what to add.
I am not sure but maybe I have to add the fact that this lookup is used on a menu item which points to an SSRS report, in the parameters section.
Update 1:
I got it working with a lookup form called like this :
Args args;
FormRun formRun;
;
args = new Args();
args.name(formstr(LookupOMOperatingUnit));
args.caller(_control);
formRun = classfactory.formRunClass(args);
formRun.init();
_control.performFormLookup(formRun);
and in the init method of this form i added:
public void init()
{
super();
element.selectMode(OMOperatingUnit_OMOperatingUnitNumber);
}
meaning the field i really need.
I am not sure i understand the mechanism completely but it seems it knows how to return this exact field to the DialogField from where it really started.
In order to make it look like a lookup, i have kept the style of the Design as Auto but changed the WindowType to Popup, HideToolBar to Yes and Frame to Border.
Probably the best route is do a custom lookup and change the extended data type of the key field to reflect that. In this way the change is reflected in all places. See form FiscalCalendarYearLookup and EDT FiscalYearName as an example of that.
If you only need to change a single place, the easy option is to override performFormLookup on the calling form. You should also override the DisplayLength property of the extended data type of the long field.
public void performFormLookup(FormRun _form, FormStringControl _formControl)
{
FormGridControl grid = _form.control(_form.controlId('grid'));
grid.autoSizeColumns(false);
super(_form,_formControl);
}
This will not help you unless you have a form, which may not be the case in this report scenario.
Starting in AX 2009 the kernel by default auto-updates the control sizes based on actual record content. This was a cause of much frustration as the sizes was small when there was no records and these sizes were saved! Also the performance of the auto-update was initially bad in some situations. As an afterthought the grid control autoSizeColumns method was provided but it was unfortunately never exposed as a property.
you can extends the sysTableLookup class and override the buildFromGridDesign method to set the grid control width.
protected void buildFormGridDesign(FormBuildGridControl _formBuildGridControl)
{
if (gridWidth > 0)
{
_formBuildGridControl.allowEdit(true);
_formBuildGridControl.showRowLabels(false);
_formBuildGridControl.widthMode(2);
_formBuildGridControl.width(gridWidth);
}
else
{
super(_formBuildGridControl);
}
}

How to trigger an action from a NSTableCellView in view based NSTableView when using bindings

I'm facing a problem with a view-based NSTableView running on 10.8 (target is 10.7, but I think this is not relevant).
I'm using an NSTableView, and I get content values for my custom NSTableCellView through bindings. I use the obejctValue of the NSTableCellView to get my data.
I added a button to my cell, and I'd like it to trigger some action when clicked. So far I have only been able to trigger an action within the custom NSTableCellView's subclass.
I can get the row that was clicked like this, using the chain:
NSButton *myButton = (NSButton*)sender;
NSTableView *myView = (NSTableView*)myButton.superview.superview.superview;
NSInteger rowClicked = [myView rowForView:myButton.superview];
From there I don't know how to reach my App Delegate or controller where the action is defined.
As I am using cocoa bindings, I do not have a delegate on the NSTableView that I could use to trigger my action.
Do you have any idea how I could talked back to controller ?
Many thanks in advance!
Although you are using bindings you can still set your controller as the delegate for your tableview in the interface builder.
I see that you already are able to access the table view from inside your cell. The next task must be simple, just set the table view delegate as the target for your button's action.
Thanks for your question, I also will be triggering an action from a button on a NSTableView. Your question helped to put me on the correct path.
First to address the your solution to finding which row number my NSTableView is on. I was able to find it without knowing the button, in my custom NSTableView I installed the following as a first attempt:
- (NSInteger)myRowNumber
{
return [(NSTableView*)self.superview.superview rowForView:self];
}
this works fine, however it is less than robust. It only works if you already know specifically how deep you are in the view hierarchy. A more robust and universal solution is:
- (NSInteger)myRowNumber
{
NSTableView* tableView = nil;
NSView* mySuperview = self;
do
{
NSView* nextSuper = mySuperview.superview;
if (nextSuper == nil)
{
NSException *exception =
[NSException exceptionWithName:#"NSTableView not found."
reason:[NSString stringWithFormat:#"%# search went too deep.",
NSStringFromSelector(_cmd)] userInfo:nil];
#throw exception;
}
if ([nextSuper isKindOfClass:[NSTableView class]])
tableView = (NSTableView*)nextSuper;
else
mySuperview = mySuperview.superview;
} while (tableView == nil);
return [tableView rowForView:self];
}
this not only works at the NSTableView level, but works with anything installed at any level above it, no matter how complex the view hierarchy.
As to the unanswered part of your question, I established an IBOutlet in my class and using interface builder tied if to my files owner (in my case my document class). Once I had a reference to the class I was sending my message to, and the row number, I call the function. In my case the call required that I pass the row number it originates from.
[self.myDoc doSomethingToRow:self.myRowNumber];
I tested this and it works at various levels of the view hierarchy above NSTableView. And it functions without having to have the row selected first (which appears to be assumed in Apples documentation).
Regards, George Lawrence Storm, Maltby, Washington, USA
Use rowForView: and the responder chain
To respond to a control's action embedded within an NSTableCellView, the control should issue the action to the First Responder. Alternatively, File Owner is possible but this is more tightly coupled.
Use rowForView: within the action method to determine which row's control issued the action:
- (IBAction)revealInFinder:(id)sender {
NSInteger row = [self.tableView rowForView:sender];
...
}
The action is implemented within any of the responder chain classes. Most likely, this will be your subclassed NSWindowController instance. The responder could also be the application delegate; assuming the delegate has a means to talk to the NSTableView.
See Apple's example TableViewPlayground: Using View-Based NSTableView and NSOutlineView to see this in action.
Suhas answer helped me.
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
if let cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "EDIT_CELL_VIEW"), owner: self) as? SymbolManagerCell {
if let editButton = cell.subviews[0] as? NSButton {
editButton.target = cell // this is required to trigger action
}
return cell
}
return nil
}

ActionScript/Flex: Augment MouseEvents with extra information

I've got a business class, Spam and the corresponding view class, SpamView.
How can I augment MouseEvents coming out of SpamView so the MouseEvents which come out of it contain a reference to the instance of Spam which the SpamView is displaying?
Here's how I'd like to use it:
class ViewContainer {
...
for each (spam in spams) {
addChild(new SpamView(spam));
...
function handleMouseMove(event:MouseEvent) {
if (event is SpamViewMouseEvent)
trace("The mouse is being moved over spam:", spam)
}
}
Thanks!
Things I've considered which don't work:
Adding event listeners to each SpamView: the book keeping (making sure that they are added/removed properly) is a pain.
Using event.target: the event's target may be a child of the SpamView (which isn't very useful)
Listening for a MouseEvent, creating a new SpamViewMouseEvent, copying all the fields over, then dispatching that: copying all the fields manually is also a pain.
There are multiple ways to solve this puppy. I would use your #2 option, but build a utility function that gets all of the spamViews on the screen and do a couple of if-elses in looping over your spamViews.
var targ : DisplayObject = DisplayObject(event.target)
If(targ is SpamView) //then you know what's up.
If( loopedSpamView.contains(targ) ) // then the target is inside the spamViewContainer and you should be cool.
Best of luck,
Jeremy

Connecting multiple signals to a single slot in Qt

I'm trying to keep track of the textChanged() signal on for handful of QTextEdits. I want to do the same thing regardless of the text edit emitting the signal: uncheck its associated checkbox in a QListWidget if it becomes empty and leave it checked otherwise. The function I have so for is as follows:
void MainWindow::changed()
{
QString tempStr = ui->hNMRedit->toPlainText();
if(tempStr != "")
{
ui->checkList->item(0)->setCheckState(Qt::Checked);
}
else
{
ui->checkList->item(0)->setCheckState(Qt::Unchecked);
}
}
With the current approach, I would have to make a function like this for every QTextEdit; each function containing virtually identical code. If I stored each of the text edits in an array (so I could find their associated index in the QListWidget), would it be possible for me to have a slot like this?
void MainWindow::changed(QWidget *sender) // for whichever text edit emits the
// textChanged() signal
{
QString tempStr = sender->toPlainText();
if(tempStr != "")
{
// I would potentially use some sort of indexOf(sender) function on the array I
// mentioned earlier here... a little new to Qt, sorry
ui->checkList->item(array.indexOf(sender))->setCheckState(Qt::Checked);
}
else
{
// same as above...
ui->checkList->item(array.indexOf(sender))->setCheckState(Qt::Unchecked);
}
}
Is this possible or should I just create a separate slot for every text edit?
Please let me know if any further clarification is needed!
Lastly, I feel like the only meaningful difference between QLineEdits and QTextEdits is the default size. In favor of keeping things consistent, should I just use one of these objects throughout my UI?
Thanks!!!
I think you are missing the point of slots and signals. How are you creating the connections?
Are you trying to check a box when any of the text boxes change? If so use a QSignalMapper to map the textChanged() signals to send a value of true and connect that to the QCheckBox setChecked(bool) slot.
If that is too complicated subclass QCheckBox and create a set of functions checkBox() uncheckBox() so you can toggle states without a variable. Then connect the QTextEdit textChanged() to your subclass checkBox()
If this is not what you are looking for, at least subclass QTextEditto take in a QCheckBox that it can change when the text changes instead of duplicating code for every QTextEdit
All you need is a hash of QAbstractButton*, keyed by QTextEdit*. In the slot, you look up the sender() in the hash, if found you've got the button you need. This is precisely what is done by the QSignalMapper: you can map from a sender QWidget* to your button QWidget*. Use qobject_cast to cast to QAbstractButton*.

Resources