Button enableWhen TableView selectedItem is not empty - tornadofx

TableView selectedItem is bind to ViewModel and i want to button be disabled when selectedItem is null and enabled otherwise.
class MainView: View("TheSubberKt") {
override val root = Form()
val model = MainViewModel()
init {
with(root) {
fieldset {
field("Media:") {
textfield(model.mediaPath)
button("...")
}
}
tableview(subs) {
// ...
bindSelected(model.selectedSubtitle)
}
hbox {
button("Hash Search")
button("Download Selected") {
// what to do here?
}
}
}
}
}
I imagine that i have to create a OvservableValue<Boolean> and then pass to enableWhen but, how to do that?
Is it possible to map a property onChange to a custom observable? (just another idea)

Observable values have a function that will return an BooleanBinding when they don't contain a value, and since you've already bound the selected item to model.selectedSubtitle, you can simply add the following expression inside of the button builder:
enableWhen(model.selectedSubtitle.isNotNull)
If you didn't bind the selected item of the TableView to a property accessible in the view, you could store a reference to the table directly and bind to state in the table's selection model:
enableWhen(table.selectionModel.selectedItemProperty().isNotNull)
On another note, you can clean up your syntax by getting rid of the init block and declare the root node directly with a builder:
override val root = tableview(subs) {
...
}
Hope this helps :)

Related

How to get Label view in ViewModel to set accessibility focus in xamarin forms

I have Label in view, I need that Label's view in my ViewModel. I am using Dependency Service to set focus on Controls for Accessibility service, DS requires view as a param.
This is my Label
<Label
AutomationProperties.IsInAccessibleTree="{Binding ShowNoResults}"
IsVisible="{Binding ShowNoResults}"
Text="{Binding ResultsHeader}"/>
I tried Command but Label doesn't support command. Below code also not working
var view = GetView() as HomeworkView;
I am getting view always null. How can I fix this?
I am not quite sure what are you trying to achieve, but you can't access the View elements from you view model.
If you want to do something with the control, you can use the messaging center to do it, here is an example
in your ViewModel
MessagingCenter.Send(this, "your message here");
then in your page, you need to subscribe to this message from that view model and do the desired action
MessagingCenter.Instance.Unsubscribe<ViewModelClassNamedel>(this, "your message here");
MessagingCenter.Instance.Subscribe<ViewModelClassName>(this, "your message here", (data) =>
{
this.youControlName.Focus();
});
More detail added to Mohammad's answer.
Message Center doc.
In your ViewModel (with class name "YourViewModel"):
// Here we use control name directly.
// OR could make an "enum" with a value for each control.
string controlName = ...;
MessagingCenter.Send<YourViewModel>(this, "focus", controlName);
then in your page, subscribe to this message and do the desired action
.xaml.cs:
protected override void OnAppearing() {
{
base.OnAppearing();
// Unsubscribe before Subscribe ensures you don't Subscribe twice, if the page is shown again.
MessagingCenter.Instance.Unsubscribe<YourViewModel>(this, "focus");
MessagingCenter.Instance.Subscribe<YourViewModel>(this, "focus", (controlName) =>
{
View v = null;
switch (controlName) {
case "name1":
v = this.name1;
break;
case "name2":
v = this.name2;
break;
}
if (v != null) {
//v.Focus();
// Tell VM to use v as view.
((YourViewModel)BindingContext).SetFocus(v);
}
});
}
protected override void OnDisappearing() {
MessagingCenter.Instance.Unsubscribe<YourViewModel>(this, "focus");
base.OnDisappearing();
}
If need to pass View v back to VM, because that has the logic to use it:
public class YourViewModel
{
public void SetFocus(View view)
{
... your code that needs label's view ...
}
}
Not tested. Might need some slight changes. Might need
...(this, "focus", (sender, controlName) =>
instead of
...(this, "focus", (controlName) =>
UPDATE
Simple approach, if there is only ONE View that is needed in VM.
public class YourViewModel
{
public View ViewToFocus { get; set; }
// The method that needs ViewToFocus.
void SomeMethod()
{
...
if (ViewToFocus != null)
... do something with it ...
}
}
public class YourView
{
public YourView()
{
InitializeComponent();
...
// After BindingContext is set.
((YourViewModel)BindingContext).ViewToFocus = this.yourLabelThatShouldBeFocused;
}
}
ALTERNATIVE: It might be cleaner/more robust to set ViewToFocus in page's OnAppearing, and clear it in OnDisappearing. This ensures it is never used while the page is not visible (or in some delayed action after the page has gone away). I would probably do it this way.
protected override void OnAppearing()
{
base.OnAppearing();
((YourViewModel)BindingContext).ViewToFocus = this.yourLabelThatShouldBeFocused;
}
protected override void OnDisappearing()
{
((YourViewModel)BindingContext).ViewToFocus = null;
base.OnDisappearing();
}

Material Datepicker change month view event

is there any way how to detect that calendar has new month view is loaded, when "yellow buttons" (see screenshot) are used?
You can listen for click events of those buttons on the calendar using Renderer2.
Code from this blog post.
constructor( private renderer: Renderer2g) { }
ngAfterViewInit() {
let buttons = document.querySelectorAll('mat-calendar .mat-calendar-previous-button,' +
'mat-calendar .mat-calendar-next-button');
if (buttons) {
Array.from(buttons).forEach(button => {
this.renderer.listen(button, "click", () => {
console.log("Month changed");
});
})
}
}
You can just use your own header component with whatever buttons and events your want:
<mat-calendar [headerComponent]="headerComponent"></mat-calendar>
And declare a variable headerComponent in the .ts:
public headerComponent = MyCoolHeaderComponent;
You will have to provide your own UI and functionality for the header (in your MyCoolHeaderComponent class).
MatDatepicker component uses DateAdapter service to handle dates, and when you click on next or previous button, a handler runs on MatCalendarHeader component and this handler calls addCalendarMonths method on DateAdapter (if current view is month) so if you patch this function on DateAdapter service TEMPORARILY, you can do anything you like, for example:
in parent component
origAddCalendarMonths: any
constructor(#Optional() private _dateAdapter: DateAdapter<any>) { }
onOpenedStream() {
this.origAddCalendarMonths = this._dateAdapter.addCalendarMonths
this._dateAdapter.addCalendarMonths = (...args) => {
console.log('!!!')
// your logic
return this.origAddCalendarMonths.apply(this._dateAdapter, args)
}
}
onClosedStream() {
this._dateAdapter.addCalendarMonths = this.origAddCalendarMonths
}
ngOnDestroy() {
this.onClosedStream()
}
or if you use MatCalendar instead, you can provide some extended class from DateAdapter in parent component providers to provide that class as DateAdapter for all child of this component (NodeInjector)

What is the proper way to have a ListView show a LINQ query and update dynamically?

I'm new to Xamarin and C#, so apologies in advance for any mistakes I make.
In my app, I have a list of plants. When a plant is selected, I have a detail view of info about the plant. In the detail view, I have a button that adds or removes the plant from a shopping list.
To implement this, I have a class named MyPlant, with a field called InCart, and a method ToggleInCart that the button calls.
(note that I didn't paste in some code to simplify this question as much as possible)
public class MyPlant : INotifyPropertyChanged
{
string name;
bool inCart;
...
public bool InCart
{
set
{
if (inCart != value)
{
inCart = value;
OnPropertyChanged("InCart");
}
}
get { return inCart; }
}
public ICommand ToggleCartStatus
{
get
{
if (_toggleCartStatus == null)
{
_toggleCartStatus = new Command(() => InCart = !InCart);
}
return _toggleCartStatus;
}
I have another class called PlantList, which has a method PlantsInCart that uses LINQ to return an ObservableCollection of MyPlant where InCart is true.
public class PlantList : INotifyPropertyChanged
{
public ObservableCollection PlantsInCart
{
private set { }
get
{
ObservableCollection list = new ObservableCollection(myPlants.Where(i => i.InCart));
return list;
}
}
In my XAML, I have a ListView bound to PlantsInCart.
Everything works as I want EXCEPT when I remove the selected plant, the list doesn't update to show the plant is missing even though the data underneath it is correctly updated. If I refresh the list by going to a different page and coming back, then the list shows the right plants.
I suspect this doesn't work because the change in the InCart field isn't bubbling up high enough to that the ListView hears that it is supposed to update.
Can anybody advise me on the proper way to implement this kind of feature? In other words, how should you implement a scenario where you have a list that should update when a property of an item in the list changes?

Set different binding context for custom user control

I am trying to create my first Xamarin.Forms custom user control named LocationSelector. It has an Entry and when the user enters something, a list with selectable locations is shown (similary to the selection in Google Maps).
The selected location is the important 'return value' of the control.
My plan is to catch the ItemSelected event of the list and set the SelectedLocation property. LocationSelector is designed as MVVM and since everything is working so far here just the Code-Behind (which I think is enough to describe the problem):
public partial class LocationSelector : StackLayout
{
public static readonly BindableProperty SelectedLocationProperty =
BindableProperty.Create<LocationSelector, LocationModel>(s => s.SelectedLocation, new LocationModel(), BindingMode.TwoWay);
public LocationSelector()
{
InitializeComponent();
var model = new LocationSelectorModel();
BindingContext = model;
_listView.ItemSelected += (sender, args) =>
{
SelectedLocation = model.SelectedLocation;
};
}
public LocationModel SelectedLocation
{
get { return (LocationModel)GetValue(SelectedLocationProperty); }
set { SetValue(SelectedLocationProperty, value); }
}
}
Now I want to use this control on a search view where the BindingContext is set to the SearchViewModel:
<ContentPage x:Class="Application.App.Views.SearchView" ...>
<c:LocationSelector SelectedLocation="{Binding Location}"/>
</ContentPage>
public class SearchViewModel : ViewModel
{
private LocationModel _location;
public LocationModel Location
{
get { return _location; }
set { SetProperty(ref _location, value); }
}
}
Unfortunately this is not working. The output throws a binding warning:
Binding: 'Location' property not found on 'Application.App.CustomControls.LocationSelectorModel', target property: 'Application.App.CustomControls.LocationSelector.SelectedLocation'
Why points the binding to a property in the ViewModel that is used 'within' my custom control and not to the property in the BindingContext of the view?
The problem is setting the BindingContext to the view model of the user control:
public LocationSelector()
{
var model = new LocationSelectorModel();
BindingContext = model; // this causes the problem
// ...
}
In this post I found the solution. Setting the BindingContext to each child elements rather than the whole user control is doing the job:
public LocationSelector()
{
var model = new LocationSelectorModel();
foreach (var child in Children)
{
child.BindingContext = model;
}
}

Receive Notifications from Child ViewModels

I might be missing something simple, so bear with me.
I have a ViewModel that contains the following:
public ObservableCollection<Person> PersonCollection
{
get { return personCollection; }
set
{
if (personCollection != value)
{
personCollection = value;
RaisePropertyChanged("PersonCollection");
}
}
}
Then in another ViewModel I have:
public ObservableCollection<Person> PersonCollection
{
get
{
PersonViewModel vm = (App.Current.Resources["Locator"] as ViewModelLocator).PersonViewModel;
return vm.PersonCollection;
}
}
public PersonViewModel PersonViewModel
{
get
{
return ((App.Current.Resources["Locator"] as ViewModelLocator).PersonViewModel)
}
}
In my XAML if I bind to PersonCollection then updates don't happen on my view, but if I bind to PersonViewModel.PersonCollection it does. so is this the "proper" way to do it or is there a way for the view to detect the notifications using the first approach?
Change your binding to {Binding PersonViewModel.PersonCollection}
Your wrapped PersonCollection property has no change notifications, so the view doesn't know that the property has changed (it certainly has no way of knowing it originally came from PersonViewModel in order to get change notifications from it)

Resources