Caliburn Micro MVVM getting comboBox to display a ViewModel screen - mvvm

I have a combobox displaying the lists within it perfectly in the ViewModel but I'm looking to have it so that when a selected item from the list is chosen it fires the ViewModel screen and I only want one from the list to do this?
So here is what I have in the ChooseView:
<ComboBox x:Name="CatalogName1" SelectedItem="{Binding SelectedCatalog1}" Style="{DynamicResource appComboBox}" Grid.Row="1" Grid.Column="1" >
</ComboBox>
and in the ChooseViewModel:
public List<string> CatalogName1
{
get
{
return new List<string> { "New", "Replace", "Extended", "Nothing", "ShowScreen" };
}
}
private string selectedCatalog1;
public string SelectedCatalog1
{
get
{
return this.selectedCatalog1;
}
set
{
this.selectedCatalog1 = value;
this.NotifyOfPropertyChange(() => this.SelectedCatalog1);
}
}
the "ShowScreen" in the combo list should display the ShowScreenViewModel but I have tried with the getter setter and it's not making sense to me

Okay, this is the way I would fix the problem...
private string selectedCatalog1;
public string SelectedCatalog1
{
get
{
return selectedCatalog1;
}
set
{
selectedCatalog1 = value;
ValidateValue(value);
NotifyOfPropertyChange(() => SelectedCatalog1);
}
}
private void ValidateValue(string s)
{
if (s == "ShowScreen")
{
ActivateItem(new ShowScreenViewModel());
}
}

Related

Make focus go to next entry in a collection view

I have an application in .Net Maui that uses a collection view with an entry field and after the collection view one static entry field. If you are currently focused on the first entry in the collection view and hit tab or enter it will not navigate to the next entry in the collection view and focus on the static entry field. I need to find the best way to have the entry focus on the next entry in the collection view on complete.
I have tried changing the return type of the collection view entry field to Next and also tried the community toolkit SetFocusOnEntryCompletedBehavior function and both result in the same behavior of not navigating to the next entry from the collection view. Very similar to this issue that doesnt seem to be resolved. MAUI - CollectionView jump / focus to next entry
I found a workaround for you. You could try the following code:
Step1 Create a custom control , let's call it MyEntry (MyEntry.cs) which subclass Entry:
In this control we attach a BindableProperty IsExpectedToFocusProperty which we used it to judge whether it is goning to be focused. We also registered a new method OnIsExpectedToFocus to detect propertyChanged for our control. For info about BindableProperty, you could refer to Bindable properties.
MyEntry.cs,
public class MyEntry : Entry
{
public static readonly BindableProperty IsExpectedToFocusProperty = BindableProperty.Create("IsExpectedToFocus", typeof(bool), typeof(MyEntry), false, propertyChanged:OnIsExpectedToFocus);
public bool IsExpectedToFocus
{
get => (bool)GetValue(IsExpectedToFocusProperty);
set => SetValue(IsExpectedToFocusProperty, value);
}
static void OnIsExpectedToFocus(BindableObject bindable, object oldValue, object newValue)
{
// Property changed implementation goes here
if ((bool)newValue == true)
{
(bindable as Entry).Focus();
}
}
}
Step2 Consume custom control in CollectionView. We define the ReturnCommand and its parameter. we will bind them in the MainPageViewModel.
MainPage.xaml,
<CollectionView x:Name="mycoll" ItemsSource="{Binding ItemCollection}">
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout>
<local:MyEntry x:Name="myentry" Focused="myentry_Focused"
IsExpectedToFocus="{Binding IsExpectedToFocus}"
Text="{Binding Title,Mode=TwoWay}" TextColor="Black"
ReturnCommand="{Binding Source={RelativeSource AncestorType={x:Type local:MainPageViewModel}}, Path=ReturnCommand}"
ReturnCommandParameter="{Binding .}"/>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
In .cs file:
void myentry_Focused(System.Object sender, Microsoft.Maui.Controls.FocusEventArgs e)
{
var entry = sender as Entry;
foreach (var item in viewModel.ItemCollection)
{
if (entry.BindingContext != item)
{
item.IsExpectedToFocus = false;
}
}
}
Step3 Design our MainPageViewModel. I define an ObservableCollection which ItemSource will bind to. And add three items just for test.
Then I think the most important part is to design the Command. Let me explain it briefly. When we press the entry of an Entry, we fire the ReturnCommand and get current Item through ReturnCommandParameter. We get the index of current Item in ItemCollection. So the next entry which needs to be focused corresponds to the index+1 Item. Then we changed the IsExpectedToFocus of the next entry and fire the OnIsExpectedToFocus method which set the entry be focused. Done!
MainPageViewModel.cs
public class MainPageViewModel
{
public ObservableCollection<Item> ItemCollection { get; set; } = new ObservableCollection<Item>();
public Command ReturnCommand
{
get
{
return new Command<Item>((e) =>
{
e.IsExpectedToFocus = false;
int index = ItemCollection.IndexOf(e); // get the current index
if (index != -1)
{
int nextIndex;
// if last entry, next index is 0, else index +1
if (index < (ItemCollection.Count() - 1))
{
nextIndex = index + 1;
ItemCollection[nextIndex].IsExpectedToFocus = true;
}
else if(index == (ItemCollection.Count() - 1))
{
nextIndex = 0;
ItemCollection[nextIndex].IsExpectedToFocus = true;
}
}
});
}
}
public MainPageViewModel()
{
//add three item for test
ItemCollection.Add(
new Item
{
Title = "12345",
IsExpectedToFocus = false
}) ;
ItemCollection.Add(
new Item
{
Title = "23456",
IsExpectedToFocus = false
});
ItemCollection.Add(
new Item
{
Title = "34567",
IsExpectedToFocus = false
});
}
}
Also, this is Item.cs, should implement INotifyPropertyChanged
public class Item : INotifyPropertyChanged
{
public string title;
public bool isExpectedToFocus;
public string Title
{
get
{
return title;
}
set
{
title = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Title)));
}
}
public bool IsExpectedToFocus
{
get
{
return isExpectedToFocus;
}
set
{
isExpectedToFocus = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsExpectedToFocus)));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Hope it works for you.

xamarin form: Custom View in listview on android screw up

I found xamarin render ui is very difficult.
I have a custom view inside the listview which contain a list of small icons. Each item may have some visible icon showing. I realized when i scrolled down to bottom and scroll back to the top, the one supposed to have that icon to be shown was not shown. I passed in an object into the custom view to tell which one to be displayed.
Item in listview, local:ChildInfoIconsView is the custom view.
<StackLayout Orientation="Vertical"
Grid.Row="0"
Grid.Column="1"
VerticalOptions="Center"
HorizontalOptions="Start">
<Label AutomationId="aChildName" Style="{StaticResource MediumBoldFont}" x:Name="childName" Text="{Binding DisplayName}" HorizontalOptions="StartAndExpand" />
<local:ChildInfoIconsView
Child="{Binding .}"
VerticalOptions="Fill">
</local:ChildInfoIconsView>
</StackLayout>
Main Code for custom view
public partial class ChildInfoIconsView : ContentView
{
public static readonly BindableProperty OrientationProperty = BindableProperty.Create(nameof(Orientation), typeof(StackOrientation), typeof(StackOrientation), StackOrientation.Vertical, BindingMode.OneWay, null);
public static readonly BindableProperty ShowClassroomProperty = BindableProperty.Create(nameof(ShowClassroom), typeof(bool), typeof(bool), true, BindingMode.OneWay, null);
public static readonly BindableProperty ChildProperty = BindableProperty.Create(nameof(Child), typeof(ChildModel), typeof(ChildModel), null, BindingMode.OneWay, null);
protected override void OnPropertyChanged(string propertyName = null)
{
base.OnPropertyChanged(propertyName);
if (propertyName.Equals(nameof(Child)))
{
if (Child != null)
{
this.ImageUnauth.IsVisible = (Child.HasUnauthorized.HasValue) ? Child.HasUnauthorized.Value : false;
this.ImageMedical.IsVisible = (Child.HasMedical.HasValue) ? Child.HasMedical.Value : false;
this.ImageBirthday.IsVisible = Child.IsBirthday;
this.ImageNewChild.IsVisible = !string.IsNullOrEmpty(Child.NewChildFlagImage);
this.ImageClassroom.IsVisible = (!ShowClassroom) ? false : !string.IsNullOrEmpty(Child.Classroom);
this.textClass.Text = Child.Classroom;
this.textClass.IsVisible = (!ShowClassroom) ? false : !string.IsNullOrEmpty(Child.Classroom);
}
}
else if (propertyName.Equals(nameof(ShowClassroom)))
{
this.ImageClassroom.IsVisible = ShowClassroom;
this.textClass.IsVisible = ShowClassroom;
}
else if (propertyName.Equals(nameof(Orientation)))
{
this.layout.Orientation = Orientation;
}
}
public ChildInfoIconsView()
{
InitializeComponent();
}
public StackOrientation Orientation
{
get
{
return (StackOrientation)GetValue(OrientationProperty);
}
set
{
SetValue(OrientationProperty, value);
}
}
public bool ShowClassroom
{
get
{
return (bool)GetValue(ShowClassroomProperty);
}
set
{
SetValue(ShowClassroomProperty, value);
}
}
public ChildModel Child
{
get
{
return (ChildModel)GetValue(ChildProperty);
}
set
{
SetValue(ChildProperty, value);
}
}
It work fine when using old version of xamarin.forms. When updated the package, this happen on android.

Best practice for nesting ContentViews into ContentPage as Xamarin architecture

I am have an app that basically loads a bunch of ContentViews into the home ContentPage. I THINK there is a problem here because all of the viewmodels would essentially need to been initialized every time we load the home page. I am wondering if it is worth my time to ditch the below code and convert the views from ContentView's to ContentPage's and just do Navigation.PushAsync(new View1()); instead. Sorry I know this is alot of example code but I would really like to get a clear picture of best practice.
My Home.xaml
<Grid x:Name="ContentBody" VerticalOptions="FillAndExpand">
<local:View1 Grid.Row="0" x:Name="View1" IsVisible="{Binding View1IsVisible}" BindingContext="{Binding View1ViewModel}" />
<local:View2 Grid.Row="0" x:Name="View2" IsVisible="{Binding View2IsVisible}" BindingContext="{Binding View2ViewModel}" />
<local:View3 Grid.Row="0" x:Name="View3" IsVisible="{Binding View3IsVisible}" BindingContext="{Binding View3ViewModel}" />
<local:View4 Grid.Row="0" x:Name="View4" IsVisible="{Binding View4IsVisible}" BindingContext="{Binding View4ViewModel}" />
<local:View5 Grid.Row="0" x:Name="View5" IsVisible="{Binding View5IsVisible}" BindingContext="{Binding View5ViewModel}" />
<local:View6 Grid.Row="0" x:Name="View6" IsVisible="{Binding View6IsVisible}" BindingContext="{Binding View6ViewModel}" />
<local:DrawerView Grid.Row="0" x:Name="DrawerView" IsVisible="{Binding DrawerViewIsVisible}" />
</Grid>
Then In my HomeViewModel...
private readonly View1ViewModel _view1ViewModel = new View1ViewModel();
public View1ViewModel View1ViewModel { get { return _view1ViewModel; } }
private readonly View2ViewModel _view2ViewModel = new View2ViewModel();
public View2ViewModel View2ViewModel { get { return _view2ViewModel; } }
private readonly View3ViewModel _view3ViewModel = new View3ViewModel();
public View3ViewModel View3ViewModel { get { return _view3ViewModel; } }
private readonly View4ViewModel _view4ViewModel = new View4ViewModel();
public View4ViewModel View4ViewModel { get { return _view4ViewModel; } }
private readonly View5ViewModel _view5ViewModel = new View5ViewModel();
public View5ViewModel View5ViewModel { get { return _view5ViewModel; } }
private readonly View6ViewModel _view6ViewModel = new View6ViewModel();
public View6ViewModel View6ViewModel { get { return _view6ViewModel; } }
///////////////////Some Visibility Properties...//////////////////////
///////////////////Some Visibility Properties...//////////////////////
private bool _view1IsVisible;
public bool View1IsVisible
{
get { return _view1IsVisible; }
set { _view1IsVisible = value; OnPropertyChanged("View1IsVisible"); }
}
private bool _view2IsVisible;
public bool View2IsVisible
{
get { return _view2IsVisible; }
set { _view2IsVisible = value; OnPropertyChanged("View2IsVisible"); }
}
private bool _view3IsVisible;
public bool View3IsVisible
{
get { return _view3IsVisible; }
set { _view3IsVisible = value; OnPropertyChanged("View3IsVisible"); }
}
private bool _view4IsVisible;
public bool View4IsVisible
{
get { return _view4IsVisible; }
set { _view4IsVisible = value; OnPropertyChanged("View4IsVisible"); }
}
private bool _view5IsVisible;
public bool View5IsVisible
{
get { return _view5IsVisible; }
set { _view5IsVisible = value; OnPropertyChanged("View5IsVisible"); }
}
private bool _view6IsVisible;
public bool View6IsVisible
{
get { return _view6IsVisible; }
set { _view6IsVisible = value; OnPropertyChanged("View6IsVisible"); }
}
/////And then this is more or less a method to show the view/////////////
private void ShowView(ViewChangedEventArgs e)
{
HideAllViews();
switch(e.SelectedView){
case ViewType.View1:
View1IsVisible = true
break;
case ViewType.View2:
View2IsVisible = true
break;
case ViewType.View3:
View3IsVisible = true
break;
case ViewType.View4:
View4IsVisible = true
break;
case ViewType.View5:
View5IsVisible = true
break;
case ViewType.View6:
View6IsVisible = true
break;
}
}
Can someone tell me if this approach is fine? as using this approach everytime I add a new page I will need to add a view to the Homepage view and the viewModel & IsVisible properties to the Homepage ViewModel..
I would greatly appreciate any guidance on this. I think a better approach would be to just seperate the ContentViews from the HomePage and when I Navigate to one of these views I would just PushAsync. I have seen supporting documentation online where some people are taking the above approach, I am just trying to ask the experts what they think when they see this code.
After speaking w/ a member of the Xamarin team I've been told to remove all the views from this homeview page, bind the perspective view models in their own code behinds, and from there I can navigation using Navigation.PushAsync(new View1());
Tutorial:
https://github.com/XamarinUniversity/XAM290
Documentaiton:
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/enterprise-application-patterns/

Xamarin.Forms: Is it possible to dynamically add rows to a grid without the ViewModel calling into the VIew?

On a page where I am displaying a receipt to a user, I have a section which lists the subtotal, any taxes, and the total. Since the taxes applicable vary by region and based on the products being sold, the number of rows in this section will vary. For example, here are 3 separate orders, one with GST and PST, one with just GST, and one with neither:
I've accomplished this by putting just the SubTotal in the grid in XAML, and adding the rest in the code-behind in a method I call from the ViewModel. However, I'd really like to avoid doing it this way, so I'm wondering if there is an approach to accomplishing this which doesn't require having the ViewModel know about the View.
A ListView is not suitable here for a number of reasons:
These controls are inside of a ScrollView, and having a ListView inside of a ScrollView causes all sorts of weird problems.
I would like to keep the columns as narrow as their widest element. This is possible with a Grid, but a ListView would take up the entire width of its parent no matter what.
I neither need nor want for my rows to be selectable
So is there a way I can do this without the ViewModel knowing about the View and without resorting to using a ListView?
One way to encapsulate the functionality you require so that the view and the view model are not coupled is by creating a user control.
I created a new user control called TotalsGridControl. Here is the XAML
<?xml version="1.0" encoding="UTF-8"?>
<Grid xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ScratchPad.UserControls.TotalsGridControl"
x:Name="TotalsGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
</Grid>
And here is the code behind.
public partial class TotalsGridControl : Grid
{
public TotalsGridControl()
{
InitializeComponent();
}
public static readonly BindableProperty TotalsProperty =
BindableProperty.Create(nameof(Totals), typeof(List<TotalItem>), typeof(TotalsGridControl), null,
BindingMode.OneWay, null, OnTotalsChanged);
private static void OnTotalsChanged(BindableObject bindable, object oldvalue, object newvalue)
{
var control = (TotalsGridControl)bindable;
if (control != null)
{
if (newvalue is List<TotalItem> totals)
{
var rowNumber = -1;
double grandTotal = 0;
foreach (var totalItem in totals)
{
grandTotal += totalItem.Value;
var descLabel = new Label {Text = totalItem.Description};
var valueLabel = new Label { Text = totalItem.Value.ToString("c") };
rowNumber++;
control.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto});
control.Children.Add(descLabel, 0, rowNumber);
control.Children.Add(valueLabel, 1, rowNumber);
}
var grandTotalDescLabel = new Label { Text = "Total" };
var grandTotalValueLabel = new Label { Text = grandTotal.ToString("c") };
rowNumber++;
control.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
control.Children.Add(grandTotalDescLabel, 0, rowNumber);
control.Children.Add(grandTotalValueLabel, 1, rowNumber);
}
}
}
public List<TotalItem> Totals
{
get => (List<TotalItem>)GetValue(TotalsProperty);
set => SetValue(TotalsProperty, value);
}
}
I used a bindable property to allow a list of TotalItem to be bound to the user control.
Here is the data in the view model
public List<TotalItem> Totals { get; set; }
Totals = new List<TotalItem>
{
new TotalItem {Description = "SubTotal", Value = 99.91},
new TotalItem {Description = "GST", Value = 5.0},
new TotalItem {Description = "PST", Value = 4.9}
};
and here is the XAML in the page
<userControls:TotalsGridControl Totals="{Binding Totals}"/>
And the output
Before:
public class ReceiptPageModel : PageModelBase
{
private Receipt _receipt;
public Receipt Receipt
{
get => _receipt;
private set => Set(ref _receipt, value);
}
public override void Init(object initData)
{
Receipt = (Receipt) initData;
((ReceiptPage) CurrentPage).AddTaxes(Receipt);
}
}
After:
public class ReceiptPageModel : PageModelBase
{
private Receipt _receipt;
public Receipt Receipt
{
get => _receipt;
private set => Set(ref _receipt, value);
}
public override void Init(object initData)
{
Receipt = (Receipt) initData;
}
}
public partial class ReceiptPage : FreshBaseContentPage
{
public ReceiptPage()
{
InitializeComponent();
BindingContextChanged += HandlePageModelAdded;
}
private void HandlePageModelAdded(object sender, EventArgs e)
{
var pageModel = (ReceiptPageModel)BindingContext;
if (pageModel.Receipt != null)
{
AddTaxes(pageModel.Receipt);
}
else
{
pageModel.PropertyChanged += (s, args) =>
{
if (args.PropertyName == nameof(pageModel.Receipt))
AddTaxes(pageModel.Receipt);
};
}
}
private void AddTaxes(Receipt receipt)
{
...
}
}

How to change a radio box from true to false using MVVM

So I have a radio button that I want to change from being unchecked to being checked based on if a button is clicked. So
the XAML code I have is:
<RadioButton GroupName="rdoExchange" Grid.Row="2" Grid.Column="0" x:Name="PauseRadioButton" Command="{Binding PauseCommand}" IsChecked="{Binding Path=check, Mode=TwoWay}" Margin="3"/>
And the View Model Code:
public string check = "True";
public void Reset(object obj)
{
check = "True";
}
private ICommand m_PauseCommand;
public ICommand PauseCommand
{
get
{
return m_PauseCommand;
}
set
{
m_PauseCommand = value;
}
}
private ICommand m_ResetCommand;
public ICommand ResetCommand
{
get
{
return m_ResetCommand;
}
set
{
m_ResetCommand = value;
}
}
private void SetProperty<T>(ref T field, T value, [CallerMemberName] string name = "")
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
}
I've left out the relaycommand code and several other parts that I feel would be irrelevant to solving the problem.
Would something like this work?
XAML:
<RadioButton GroupName="rdoExchange" IsChecked="{Binding Path=OptionOneChecked, Mode=TwoWay}"/>
<Button Command="{Binding ToggleOptionOne}" Height="20" />
View Model:
public class ViewModel: NotificationObject
{
private bool _optionOneChecked;
public bool OptionOneChecked
{
get { return _optionOneChecked; }
set
{
if (value.Equals(_optionOneChecked)) return;
_optionOneChecked = value;
RaisePropertyChanged("OptionOneChecked");
}
}
public ICommand ToggleOptionOne
{
get { return new DelegateCommand(() => OptionOneChecked = !OptionOneChecked); }
}
}
using the NotificationObject class from the PRISM NuGet package.