Saving Picker Value null exception cannot convert string to model - xamarin

I am having an issue with saving of my picker value. I am rewriting old code and i am struggling with saving a picker value. Once I select the picker i have the value but it doesn't go through the getter. I have similar method on other picker it works there perfectly co I dont really understand what is wrong. The only thing is that if you notice the older piece there is the Model property.Position when filling the value. Please see my notes in the code. I have Inotify in the model as advised and in the vm
public class SettingsViewModel : ObservableObject
{
private List<CustomTextSize> _textSize = new List<CustomTextSize>();
private CustomTextSize _selectedTextSize;
public CustomTextSize SelectedTextSize
{
get => _selectedTextSize; // doesnt go through here
set
{
if (_selectedTextSize != value && _selectedTextSize != null)
{
this.IsBusy = true;
SetProperty(ref _selectedTextSize, value);
NotificationService.ShowToast("Probíhá ukládání, prosím čekejte");
UserSettings newSetting = new UserSettings()
{
DateSaved = DateTime.Now,
OptionId = (int)SettingOption.TextFontSize,
Value = value.Position.ToString()
};
new Thread(new ThreadStart(async () =>
{
await LangUpDataSaverLoader.SaveUserSettings(newSetting);
LangUpDataSaverLoader.LoadUserSettingsAndFillAppValues();
})).Start();
this.IsBusy = false;
}
}
}
public List<CustomTextSize> TextSize
{
get => _textSize;
set => SetProperty(ref _textSize, value);
}
_textSize = FillTextSizeOptions();
var customSize = _textSize.FirstOrDefault(m => m.Value == LangUpUserCustomSettings.CustomFontSize); // here should be position? But then I am getting that I cannot conevrt int to Model.
if (customSize != null)
{
_selectedTextSize = customSize;
}
}
Model
public class CustomTextSize : ObservableObject {
public int Position { get; set; }
public int Value { get; set; }
public string Text { get; set; }
}
Xaml
<Picker x:Name="textSize" WidthRequest="300"
VerticalOptions="CenterAndExpand" FontSize="20"
Title="{ grial:Translate A_PickerSizeOfText }"
ItemsSource ="{Binding TextSize}" ItemDisplayBinding="{Binding Text}"
SelectedItem ="{Binding SelectedTextSize, Mode=TwoWay}"
grial:PickerProperties.BorderStyle="Default"
BackgroundColor="Transparent"
TextColor= "{ DynamicResource ListViewItemTextColor }" >
</Picker>
Old piece of code that I am rewrting
Spinner textSizeSpinner = new Spinner(MainActivity.Instance);
ArrayAdapter textSizeAdapter = new ArrayAdapter(MainActivity.Instance, Android.Resource.Layout.SimpleSpinnerDropDownItem);
textSizeAdapter.AddAll(FillTextSizeOptions().Select(m => m.Text).ToList());
textSizeSpinner.Adapter = textSizeAdapter;
textSizeSpinner.SetBackgroundColor(Color.FromHex("#3680b2").ToAndroid());
fontSizeStepper.Children.Add(textSizeSpinner);
initialTextSizeSpinnerPosition = FillTextSizeOptions().FirstOrDefault(m => m.Value == LangUpUserCustomSettings.CustomFontSize).Position; //here is the .Position
textSizeSpinner.SetSelection(initialTextSizeSpinnerPosition);
protected async void TextSizeSelected(object sender, AdapterView.ItemSelectedEventArgs e)
{
if (e.Position != initialTextSizeSpinnerPosition)
{
dialog.SetProgressStyle(ProgressDialogStyle.Spinner);
dialog.SetCancelable(false);
dialog.SetMessage("Probíhá ukládání, prosím čekejte");
dialog.Show();
Spinner spinner = (Spinner)sender;
Model.UserSettings newSetting = new Model.UserSettings()
{
DateSaved = DateTime.Now,
OptionId = (int)SettingOption.TextFontSize,
Value = FillTextSizeOptions().First(m => m.Position == e.Position).Value.ToString(),
};
await LangUpDataSaverLoader.SaveUserSettings(newSetting);
initialTextSizeSpinnerPosition = e.Position;
LangUpDataSaverLoader.LoadUserSettingsAndFillAppValues();
dialog.Dismiss();
}
}

Related

xamarin xzing barcode scanner re-scan

I'm using the zxing barcode scanner in xamarin android forms and I can get it to scan one barcode with no issues, but I want to be able to discard the scan they have taken and have the ability to take a another scan.
I'm also using MVVM. Here is my xaml...
<Grid VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<forms:ZXingScannerView x:Name="zxingView"
IsTorchOn="{Binding TorchON}"
IsScanning="{Binding IsScanning}"
IsAnalyzing="{Binding IsAnalyzing}"
Result="{Binding Result, Mode=TwoWay}"
ScanResultCommand="{Binding ScanCommand}"
/>
<forms:ZXingDefaultOverlay
x:Name="scannerOverlay"
BottomText="Place the red line over the barcode you'd like to scan." />
<Button Grid.Row="1" Text="Toggle Flash" Command="{Binding FlashToggleCommand}"></Button>
</Grid>
And this is my page model
private string barcode = string.Empty;
public string Barcode
{
get { return barcode; }
set { barcode = value; }
}
private bool _isAnalyzing = true;
public bool IsAnalyzing
{
get { return _isAnalyzing; }
set
{
if (!Equals(_isAnalyzing, value))
{
_isAnalyzing = value;
OnPropertyChanged("IsAnalyzing");
}
}
}
private bool _isScanning = true;
private bool _torchON = false;
private DynamicContainerPageModel _hhtScreen;
private readonly IDeviceManager _deviceManager;
public ScanningViewPageModel(IDeviceManager deviceManager)
{
_deviceManager = deviceManager;
}
public override void Init(object initData)
{
base.Init(initData);
_hhtScreen = initData as DynamicContainerPageModel;
}
public bool IsScanning
{
get { return _isScanning; }
set
{
if (!Equals(_isScanning, value))
{
_isScanning = value;
OnPropertyChanged("IsScanning");
}
}
}
public bool TorchON
{
set
{
if (_torchON != value)
{
_torchON = value;
OnPropertyChanged("TorchON");
}
}
get { return _torchON; }
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public Command ScanCommand
{
get
{
return new Command(() =>
{
IsAnalyzing = false;
IsScanning = false;
Device.BeginInvokeOnMainThread(async () =>
{
Barcode = Result.Text;
var response = await CoreMethods.DisplayAlert("Barcode found", "Found: " + Result.Text, "Keep",
"Scan Again");
if (response)
{
//Save the value into the model
_deviceManager.BeginInvokeOnMainThread(() =>
{
_hhtScreen.SelectedControl.Text = barcode;
});
//close page
await this.CoreMethods.PopPageModel(false);
}
else
{
Result = null;
IsAnalyzing = true;
IsScanning = true;
}
});
IsAnalyzing = true;
IsScanning = true;
});
}
}
public Command FlashToggleCommand
{
get { return new Command(async () => { TorchON = !TorchON; }); }
}
public Result Result { get; set; }
When I press scan again on my pop up, I find it a bit hit and miss whether the scanning camera activates again or not, majority of the time it just freezes. Am I doing something wrong? Is there a better way to get the control to rescan?
I've hit a very similar problem in the past. What I had to end up doing was defining the Scanner view in the code behind, then conditionally add/remove from the view as needed.
This is what mine ended up looking like:
The XAML:
<!--
The barcode scanner grid. The actual barcode scanner is
created and added to the grid in the code behind class.
-->
<Grid x:Name="ScannerViewGrid"
Grid.Row="3"
HorizontalOptions="FillAndExpand"
IsVisible="{Binding IsBarcodeScannerRunning}"
VerticalOptions="FillAndExpand" />
The code behind:
private ZXingDefaultOverlay scannerOverlay;
private ZXingScannerView scannerView;
private void CreateNewScannerView()
{
var vm = BindingContext.DataContext as SearchViewModel;
ScannerViewGrid.Children.Clear();
scannerOverlay = null;
scannerView = null;
scannerOverlay = new ZXingDefaultOverlay();
scannerOverlay.ShowFlashButton = false;
scannerView = new ZXingScannerView();
scannerView.SetBinding(ZXingScannerView.ResultProperty, nameof(vm.BarcodeScanResult), BindingMode.OneWayToSource);
scannerView.SetBinding(ZXingScannerView.ScanResultCommandProperty, nameof(vm.BarcodeScanResultCommand));
scannerView.IsScanning = true;
ScannerViewGrid.Children.Add(scannerView);
ScannerViewGrid.Children.Add(scannerOverlay);
}
private void RemoveScannerView()
{
ScannerViewGrid.Children.Clear();
scannerView.IsScanning = false;
scannerView.IsAnalyzing = false;
scannerView.RemoveBinding(ZXingScannerView.ResultProperty);
scannerView.RemoveBinding(ZXingScannerView.ScanResultCommandProperty);
scannerView = null;
scannerOverlay = null;
}
How I solve this:
Xaml
<coreControls:ContainerLayout.Content>
<Grid>
<ContentView
x:Name="contentViewCamera"/>
<coreControls:SvgImage
SvgSource="img_qr_background"
VerticalOptions="FillAndExpand"
Aspect="AspectFill"/>
<Grid
x:Name="mainLayout"
RowDefinitions="48,*,*,*">
<contentViews:HeaderView Title="Canjear cupon"
TitleColor="{AppThemeBinding Light={StaticResource LightWhiteColor}, Dark={StaticResource DarkWhiteColor}}"
RightIconSvg="ic_flash_w"
Margin="6,0" />
<material:MaterialEntry
Grid.Row="3"
HorizontalOptions="Center"
VerticalOptions="Center"
WidthRequest="240"
HeightRequest="42"
BackgroundColor="#70000000"
BorderColor="#70000000"
Placeholder="Ingresa el codigo"/>
</Grid>
</Grid>
</coreControls:ContainerLayout.Content>
code behind
public partial class RedeemCouponPage
{
private ZXingScannerView _scannerView;
public RedeemCouponPage()
{
InitializeComponent();
}
protected override void OnAppearing()
{
base.OnAppearing();
//mainLayout.Padding = PageHelper.GetPageSafeArea(new Thickness(0, 20), this);
}
protected override void OnViewModelSet()
{
base.OnViewModelSet();
var options = new MobileBarcodeScanningOptions
{
AutoRotate = false,
TryHarder = true,
TryInverted = true,
DelayBetweenAnalyzingFrames = 5,
DelayBetweenContinuousScans = 5,
PossibleFormats = new List<BarcodeFormat>() { BarcodeFormat.QR_CODE }
};
ViewModel.InitializeCameraCommand = new MvxCommand(() =>
{
_scannerView = new ZXingScannerView()
{
Options = options,
ScanResultCommand = ViewModel.ScanResultCommand
};
_scannerView.SetBinding(ZXingScannerView.IsScanningProperty, nameof(ViewModel.IsBusy));
_scannerView.SetBinding(ZXingScannerView.IsAnalyzingProperty, nameof(ViewModel.IsBusy));
_scannerView.SetBinding(ZXingScannerView.IsTorchOnProperty, nameof(ViewModel.IsFlashActive));
_scannerView.SetBinding(ZXingScannerView.ResultProperty, nameof(ViewModel.Result), BindingMode.TwoWay);
contentViewCamera.Content = _scannerView;
});
}
}
viewModel
public class RedeemCouponViewModel: BaseViewModel
{
private readonly ICouponService CouponService = Mvx.IoCProvider.Resolve<ICouponService>();
public IMvxCommand InitializeCameraCommand { get; set; }
private ZXing.Result _result;
public ZXing.Result Result
{
get => _result;
set
{
SetProperty(ref _result, value);
BarCodeText = value.Text;
}
}
private bool _isFlashActive;
public bool IsFlashActive
{
get => _isFlashActive;
set => SetProperty(ref _isFlashActive, value);
}
private string _barCodeText;
public string BarCodeText
{
get => _barCodeText;
set => SetProperty(ref _barCodeText, value);
}
private Coupon _coupon;
public Coupon Coupon
{
get => _coupon;
set => SetProperty(ref _coupon, value);
}
public override void ViewAppeared()
{
base.ViewAppeared();
if (DeviceInfo.DeviceType == DeviceType.Physical)
InitializeCameraCommand.Execute();
IsBusy = true;
}
public IMvxAsyncCommand ScanResultCommand => new MvxAsyncCommand(async () =>
{
if (Result == null)
{
IsBusy = true;
return;
}
else
IsBusy = false;
Coupon = await CouponService.Get(BarCodeText);
PopupIsVisible = true;
}, () => IsBusy);
public IMvxCommand ConfirmRedeemCommand => new MvxCommand(()=>
{
DisplaySuccessView("Canjeado!","El cupon ha sido canjeado con exito.");
if (DeviceInfo.DeviceType == DeviceType.Physical)
InitializeCameraCommand.Execute();
PopupIsVisible = false;
IsBusy = true;
});
public IMvxCommand BackToScanerCommand => new MvxCommand(() =>
{
PopupIsVisible = false;
if (DeviceInfo.DeviceType == DeviceType.Physical)
InitializeCameraCommand.Execute();
IsBusy = true;
});
}

Highlight URL using label span - xamarin.forms

I am creating a chat application in xamarin.forms.What I am trying to achieve is whenever user typed message contains a URL, that should be highlighted and provide click to it.For this feature I found Span in Label text.When user click on send button of chat , I will check for URL and make it as another span.I got this idea from Lucas Zhang - MSFT form this question here.
The problem is I am trying to do the spanning in view model and the individual chat bubble is in another view cell which will call as ItemTemplate in my chat listview. Anyway the spanning is not working as I intended ie; it doesn't highlight .
My view Model.
public Queue<Message> DelayedMessages { get; set; } = new Queue<Message>();
public ObservableCollection<Message> Messages { get; set; } = new ObservableCollection<Message>();
public string TextToSend { get; set; }
public ChatPageViewModel()
{
OnSendCommand = new Command(() =>
{
if (!string.IsNullOrEmpty(TextToSend))
{
var urlStr = TextToSend;
int startIndex = 0, endIndex = 0;
if (urlStr.Contains("www."))
{
startIndex = urlStr.IndexOf("www.");
}
if (urlStr.Contains(".com"))
{
endIndex = urlStr.IndexOf(".com") + 3;
}
if (startIndex != 0 || endIndex != 0)
{
var formattedString = new FormattedString();
Span span1 = new Span() { Text = urlStr.Substring(0, startIndex), TextColor = Color.Black };
formattedString.Spans.Add(span1);
Span span2 = new Span() { Text = urlStr.Substring(startIndex, endIndex - startIndex + 1), TextColor = Color.LightBlue };
span2.GestureRecognizers.Add(new TapGestureRecognizer()
{
NumberOfTapsRequired = 1,
Command = new Command(() => {
})
});
formattedString.Spans.Add(span2);
Span span3 = new Span() { Text = urlStr.Substring(endIndex, urlStr.Length - 1 - endIndex), TextColor = Color.Black };
formattedString.Spans.Add(span3);
var message = new Message
{
Text = formattedString.ToString(),
IsIncoming = false,
MessageDateTime = DateTime.Now
};
Messages.Add(message);
TextToSend = string.Empty;
}
else
{
var message = new Message
{
Text = urlStr.ToString(),
IsIncoming = false,
MessageDateTime = DateTime.Now
};
Messages.Add(message);
TextToSend = string.Empty;
}
}
});
}
Single chat Bubble XAML
<Label x:Name="OutgoingMessage" TextColor="White" FormattedText="{Binding Text}" HorizontalOptions="End" >
</Label>
My Chat page XAML
<Grid RowSpacing="0" Margin="0,20,0,0"
ColumnSpacing="0">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="1" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ListView Grid.Row="0"
ItemTemplate="{StaticResource MessageTemplateSelector}"
ItemsSource="{Binding Messages,Mode=OneWay}"
Margin="0"
SelectionMode="None"
FlowDirection="RightToLeft"
HasUnevenRows="True" x:Name="ChatList"
VerticalOptions="FillAndExpand"
SeparatorColor="Transparent"
>
</ListView>
<BoxView HorizontalOptions="FillAndExpand"
HeightRequest="1"
BackgroundColor="#F2F3F5"
Grid.Row="1"/>
<partials:ChatInputBarView Grid.Row="2"
Margin="0,0,0,0"
x:Name="chatInput"/>
</Grid>
ChatPage.xaml.cs
public partial class ChatPage : ContentPage
{
ChatPageViewModel vm;
public ChatPage()
{
InitializeComponent();
this.BindingContext = vm= new ChatPageViewModel();
}
}
Messages class
public class Message : ObservableObject
{
string text;
public string Text
{
get { return text; }
set { SetProperty(ref text, value); }
}
DateTime messageDateTime;
public DateTime MessageDateTime
{
get { return messageDateTime; }
set { SetProperty(ref messageDateTime, value); }
}
public string MessageTimeDisplay => MessageDateTime.Humanize();
bool isIncoming;
public bool IsIncoming
{
get { return isIncoming; }
set { SetProperty(ref isIncoming, value); }
}
}
Any Help is appreciated.
EDIT:
This question was actually continuation of question. Previously I used AwesomeHyperLinkLabel fromlink. The problem was I cant manage the click event of that label.Thats why I moved with label span.Thanks to Leo Zhu - MSFT For the render changes.
For Android:
[assembly: ExportRenderer(typeof(AwesomeHyperLinkLabel), typeof(AwesomeHyperLinkLabelRenderer))]
namespace App18.Droid
{
public class AwesomeHyperLinkLabelRenderer : LabelRenderer
{
public AwesomeHyperLinkLabelRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
var view = (AwesomeHyperLinkLabel)Element;
if (view == null) return;
TextView textView = new TextView(Forms.Context);
textView.LayoutParameters = new LayoutParams(LayoutParams.WrapContent, LayoutParams.WrapContent);
textView.SetTextColor(view.TextColor.ToAndroid());
// Setting the auto link mask to capture all types of link-able data
textView.AutoLinkMask = MatchOptions.All;
// Make sure to set text after setting the mask
textView.Text = view.Text;
AddHyperlinksManually(textView);
//textView.SetTextSize(ComplexUnitType.Dip, (float)view.FontSize);
// overriding Xamarin Forms Label and replace with our native control
SetNativeControl(textView);
}
public static void AddHyperlinksManually(TextView _tv)
{
SpannableStringBuilder currentSpan = new SpannableStringBuilder(_tv.Text);
Linkify.AddLinks(currentSpan, MatchOptions.WebUrls);
var objects = currentSpan.GetSpans(0, currentSpan.Length(), Java.Lang.Class.FromType(typeof(URLSpan)));
var urlSpans = new URLSpan[objects.Length];
for (var i = 0; i < urlSpans.Length; i++)
{
urlSpans[i] = objects[i] as URLSpan;
}
foreach (URLSpan _url in urlSpans)
{
int iStart = currentSpan.GetSpanStart(_url);
int iEnd = currentSpan.GetSpanEnd(_url);
currentSpan.RemoveSpan(_url);
currentSpan.SetSpan(new CustomURLSpan(_url.URL), iStart, iEnd, SpanTypes.InclusiveInclusive);
_tv.SetText(currentSpan, TextView.BufferType.Normal);
_tv.MovementMethod = LinkMovementMethod.Instance;
}
}
public class CustomURLSpan : ClickableSpan
{
string mTargetURL;
public CustomURLSpan(string _url) {
mTargetURL =_url;
}
public override void OnClick(Android.Views.View widget)
{
//here you could handle the click event,and you could use MessagingCenter to send mTargetURL to your Page.
Console.WriteLine("Click");
}
}
}
The mistake was with my model.Changed string to FormattedString and also changed in the viewmodel
public class Message : ObservableObject
{
FormattedString text;
public FormattedString Text
{
get { return text; }
set { SetProperty(ref text, value); }
}
DateTime messageDateTime;
public DateTime MessageDateTime
{
get { return messageDateTime; }
set { SetProperty(ref messageDateTime, value); }
}
public string MessageTimeDisplay => MessageDateTime.Humanize();
bool isIncoming;
public bool IsIncoming
{
get { return isIncoming; }
set { SetProperty(ref isIncoming, value); }
}
}

Xamarin Forms: Flowlistview item tapping issue

I am implementing a game in my xamarin forms application, a name match game. It has two lists: one for showing the images and the other one for showing the names. The player taps the image from the top list and then taps the name from the bottom list(or name first then image). If they match the player gets points and tapped image and name will be removed from lists.
I am using flowlistview for showing the image list and name list because I need to show multiple items in a row. When tapping the image and name I have done the matching and remove the image and name if they matched. But I need to highlight the selected image or selected name when tapping and disable selection for other items. I have done highlighting feature using this thread, but it is not working perfectly. Sometimes multiple images are highlighting and sometimes when selecting an image on the top name on the below list is automatically highlighting.
I have created a sample project and uploaded it here. Please help me to complete this game. We have this game on our website, https://www.catholicbrain.com/edu-namematch/39524/1/the-two-great-commandments , please have a look at it for the working of the game. I will give you login details by DM.
Edit 1
#LucasZhang-MSFT I am accepting that, but the current question is different. It has 2 different flowlistviews. On the top an image list and on the bottom a name list. This is a simple game for kids, the player taps the image from the top list and then taps the name from the bottom list(or name first then image). If they match the player gets points and tapped image and name will be removed from lists. When not match I reset the items background colors like below:
foreach (var item1 in ImageItems)
{
item.BGColor = Color.White;
}
foreach (var item2 in NameItems)
{
item.BGColor = Color.White;
}
OnPropertyChanged("NameMatchImagItems");
OnPropertyChanged("NameMatchNameItems");
After this point, multiple images are highlighting and sometimes when selecting an image on the top name on the below list is automatically highlighting.
If you have time, can you please download the sample and have a look? I tried my best, but no luck.
Cause:
You set the ItemsSource of two flowlistview with the same source _allItems !!
Solution:
in xaml
<ContentPage.Content>
<StackLayout Orientation="Vertical">
<!--imageflowlistview-->
<flv:FlowListView
x:Name="NameMatchImageList"
FlowItemTappedCommand="{Binding ImageItemTappedCommand}"
FlowItemsSource="{Binding ImageItems}"
FlowColumnCount="2"
FlowLastTappedItem="{Binding LastImageTappedItem}"
HasUnevenRows="True">
<flv:FlowListView.FlowColumnTemplate>
<DataTemplate>
<StackLayout BackgroundColor="{Binding BGColor}" Orientation="Vertical">
<Frame
Padding="5"
Margin="5"
HasShadow="False"
BorderColor="#a4e6f9"
CornerRadius="15">
<ffimageloading:CachedImage
Source="{Binding imageUrl, Converter={StaticResource urlJoinConverter}}"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"
HeightRequest="100"
Aspect="AspectFill"/>
</Frame>
</StackLayout>
</DataTemplate>
</flv:FlowListView.FlowColumnTemplate>
</flv:FlowListView>
<!--NamesFlowlistview-->
<flv:FlowListView
x:Name="NameMatchNameList"
FlowItemTappedCommand="{Binding NameItemTappedCommand}"
FlowItemsSource="{Binding NameItems}"
FlowColumnCount="2"
FlowLastTappedItem="{Binding LastNameTappedItem}"
HasUnevenRows="True">
<flv:FlowListView.FlowColumnTemplate>
<DataTemplate>
<StackLayout Orientation="Vertical">
<Label
TextColor="Black"
FontSize="Large"
BackgroundColor="{Binding BGColor}"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center"
Text="{Binding name}"/>
</StackLayout>
</DataTemplate>
</flv:FlowListView.FlowColumnTemplate>
</flv:FlowListView>
</StackLayout>
</ContentPage.Content>
in code behind
namespace FlowListView_Tap
{
class NameMatchViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<NameMatchList> imageItems;
public ObservableCollection<NameMatchList> ImageItems { get
{
return imageItems;
}
set {
if(value!=null)
{
imageItems = value;
OnPropertyChanged("ImageItems");
}
}
}
public ObservableCollection<NameMatchList> nameItems;
public ObservableCollection<NameMatchList> NameItems
{
get
{
return nameItems;
}
set
{
if (value != null)
{
nameItems = value;
OnPropertyChanged("NameItems");
}
}
}
public bool isImageSelected = false;
public bool isNameSelected = false;
public ICommand NameItemTappedCommand { get; set; }
public ICommand ImageItemTappedCommand { get; set; }
private NameMatchList lastImageTappedItem;
public NameMatchList LastImageTappedItem
{
get
{
return lastImageTappedItem;
}
set
{
if(value!=null)
{
lastImageTappedItem = value;
OnPropertyChanged("LastImageTappedItem");
}
}
}
private NameMatchList lastNameTappedItem;
public NameMatchList LastNameTappedItem
{
get
{
return lastNameTappedItem;
}
set
{
if (value != null)
{
lastNameTappedItem = value;
OnPropertyChanged("LastNameTappedItem");
}
}
}
public NameMatchViewModel()
{
ImageItemTappedCommand = new Command((obj) => {
try
{
//reset the bg color
foreach (var item in ImageItems)
{
item.BGColor = Color.White;
}
NameMatchList imageList = obj as NameMatchList;
int index = ImageItems.IndexOf(imageList);
imageList.BGColor = Color.Red;
///ImageItems.RemoveAt(index);
//ImageItems.Insert(index, imageList);
//Storing name and imageurl to local db
Application.Current.Properties["NameMatchImageList_Image"] = imageList.imageUrl;
Application.Current.Properties["NameMatchImageList_Name"] = imageList.name;
Application.Current.Properties["ImageItem"] = imageList;
isImageSelected = true;
if (isImageSelected && isNameSelected)
{
//If both image and name selected by player startes checking the matching
StartNameMatchCheck(imageList);
}
}
catch (Exception imagetapEx)
{
Debug.WriteLine("imagetapEx:>>" + imagetapEx);
}
});
NameItemTappedCommand = new Command((obj) => {
try
{
//reset the bg color
foreach (var item in NameItems)
{
item.BGColor = Color.White;
}
NameMatchList nameList = obj as NameMatchList;
int index = NameItems.IndexOf(nameList);
nameList.BGColor = Color.Red;
//NameItems.RemoveAt(index);
//NameItems.Insert(index, nameList);
//Storing name and imageurl to local db
Application.Current.Properties["NameMatchNameList_Image"] = nameList.imageUrl;
Application.Current.Properties["NameMatchNameList_Name"] = nameList.name;
Application.Current.Properties["NameItem"] = nameList;
isNameSelected = true;
if (isImageSelected && isNameSelected)
{
//If both image and name selected by player startes checking the matching
StartNameMatchCheck(nameList);
}
}
catch (Exception nametapEx)
{
Debug.WriteLine("nametapEx:>>" + nametapEx);
}
});
}
public async void StartNameMatchCheck(NameMatchList item)
{
isImageSelected = false;
isNameSelected = false;
//Fetching data from local db
string NameMatchImageListImage = Application.Current.Properties["NameMatchImageList_Image"].ToString();
string NameMatchImageListName = Application.Current.Properties["NameMatchImageList_Name"].ToString();
string NameMatchNameListImage = Application.Current.Properties["NameMatchNameList_Image"].ToString();
string NameMatchNameListName = Application.Current.Properties["NameMatchNameList_Name"].ToString();
//Match check
if ((NameMatchImageListImage == NameMatchNameListImage) && (NameMatchImageListName == NameMatchNameListName))
{
await Application.Current.MainPage.DisplayAlert("Alert", "Success", "Ok");
//Removing the items from list if they match
ImageItems.Remove(LastImageTappedItem);
NameItems.Remove(LastNameTappedItem);
LastImageTappedItem = null;
LastNameTappedItem = null;
}
else
{
await Application.Current.MainPage.DisplayAlert("Alert", "Failed", "Ok");
//resetting the colors
LastImageTappedItem.BGColor = Color.White;
LastNameTappedItem.BGColor = Color.White;
}
}
public async void CallNameMatch()
{
try
{
//HttpClient client = new HttpClient();
//var nameMatchResponse = await client.GetAsync("");
//if (nameMatchResponse.IsSuccessStatusCode)
//{
// var Response = await nameMatchResponse.Content.ReadAsStringAsync();
// var imageResponse = JsonConvert.DeserializeObject<Games>(Response.ToString());
// var namematch = JsonConvert.DeserializeObject<Games>(Response.ToString());
ImageItems = new ObservableCollection<NameMatchList>();
ImageItems.Add(new NameMatchList() { name = "Comfort the Sorrowing", imageUrl = "/cbrain-app/files/doc-lib/2018/02/22/11/46/06/971/head/Comfort the Sorrowing.png" });
ImageItems.Add(new NameMatchList() { name = "Giving Food To The Hungry", imageUrl = "/cbrain-app/files/doc-lib/2018/02/22/11/46/23/784/head/Giving Food To The Hungry.png" });
ImageItems.Add(new NameMatchList() { name = "Pray for the Living and The Dead", imageUrl = "/cbrain-app/files/doc-lib/2018/02/22/11/46/39/707/head/Pray for the Living and The Dead.png" });
ImageItems.Add(new NameMatchList() { name = "To bury the Dead", imageUrl = "/cbrain-app/files/doc-lib/2018/02/22/11/46/54/828/head/To bury the Dead.png" });
//shuffling image list
//Random r1 = new Random();
//int randomIndex1 = 0;
//while (ImageItems.Count > 0)
//{
// randomIndex1 = r1.Next(0, ImageItems.Count);
// ImageItems[randomIndex1].BGColor = Color.White;
// ImageItems.Add(ImageItems[randomIndex1]);
// ImageItems.RemoveAt(randomIndex1);
//}
//NameMatchImagItems = new ObservableCollection<NameMatchList>(ImageItems);
NameItems = new ObservableCollection<NameMatchList>();
NameItems.Add(new NameMatchList() { name = "To bury the Dead", imageUrl = "/cbrain-app/files/doc-lib/2018/02/22/11/46/54/828/head/To bury the Dead.png" });
NameItems.Add(new NameMatchList() { name = "Pray for the Living and The Dead", imageUrl = "/cbrain-app/files/doc-lib/2018/02/22/11/46/39/707/head/Pray for the Living and The Dead.png" });
NameItems.Add(new NameMatchList() { name = "Comfort the Sorrowing", imageUrl = "/cbrain-app/files/doc-lib/2018/02/22/11/46/06/971/head/Comfort the Sorrowing.png" });
NameItems.Add(new NameMatchList() { name = "Giving Food To The Hungry", imageUrl = "/cbrain-app/files/doc-lib/2018/02/22/11/46/23/784/head/Giving Food To The Hungry.png" });
//shuffling name list
//Random r2 = new Random();
//int randomIndex2 = 0;
//while (NameItems.Count > 0)
//{
// randomIndex2 = r2.Next(0, NameItems.Count);
// NameItems[randomIndex2].BGColor = Color.White;
// NameItems.Add(NameItems[randomIndex2]);
// NameItems.RemoveAt(randomIndex2);
//}
// NameMatchNameItems = new ObservableCollection<NameMatchList>(NameItems);
//}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("NMException:>" + ex);
}
}
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public void ShowAlert(string message)
{
Device.BeginInvokeOnMainThread(async () => {
await Application.Current.MainPage.DisplayAlert("Alert", message, "Ok");
});
}
}
}

Template bound propertyChanged event is only called once on a page containing four of the templates

I have a template that I use for a button. I placed four of these buttons on a page like this:
<template:Button Meta="gst" Grid.Column="1" Selected="{Binding Mode[0].Selected}" Text="{Binding Mode[0].Name}" TapCommand="{Binding ModeBtnCmd }" />
<template:Button Meta="gst" Grid.Column="2" Selected="{Binding Mode[1].Selected}" Text="{Binding Mode[1].Name}" TapCommand="{Binding ModeBtnCmd }" />
<template:Button Meta="gst" Grid.Column="3" Selected="{Binding Mode[2].Selected}" Text="{Binding Mode[2].Name}" TapCommand="{Binding ModeBtnCmd }" />
<template:Button Meta="gst" Grid.Column="4" Selected="{Binding Mode[3].Selected}" Text="{Binding Mode[3].Name}" TapCommand="{Binding ModeBtnCmd }" />
I declare an array called Mode that I use to store some values for the Selected parameter in an array declared like this:
public partial class HomePageViewModel : ObservableObject
{
ParamViewModel[] _mode;
public ParamViewModel[] Mode { get => _mode; set => SetProperty(ref _mode, value); }
In the OnAppearing I set the value of Selected for each element in the array:
protected async override void OnAppearing()
{
base.OnAppearing();
vm.Mode[0].Selected = false;
vm.Mode[1].Selected = false;
vm.Mode[2].Selected = true;
vm.Mode[3].Selected = false;
However I do not see the HandleSelectedPropertyChanged called for each of the Buttons. If I set a debug point in the HandleSelectedPropertyChanged event, I only see it being called once.
I expect only the 3rd button to be selected but when the code runs the buttons appear as:
Selected Selected Selected Selected
Can someone give me advice on this. The code works when I click on the buttons one by one and the back-end changes the selected and then the above SetButtons is called. But it doesn't work initially and all buttons appear as selected.
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace Japanese.Templates
{
public partial class Button : Frame
{
public static readonly BindableProperty TapCommandProperty = BindableProperty.Create("TapCommand", typeof(Command), typeof(Button), defaultBindingMode: BindingMode.TwoWay, defaultValue: default(Command));
public static readonly BindableProperty TapCommandParamProperty = BindableProperty.Create(nameof(TapCommandParam), typeof(object), typeof(Button), default(object));
public static readonly BindableProperty
SelectedProperty = BindableProperty.Create(nameof(Selected),
typeof(bool),
typeof(Button),
false,
propertyChanged: HandleSelectedPropertyChanged);
public static readonly BindableProperty
TextProperty = BindableProperty.Create(nameof(Text),
typeof(string),
typeof(Button),
default(string));
public static readonly BindableProperty
MetaProperty = BindableProperty.Create("Meta",
typeof(string),
typeof(Button),
default(string),
propertyChanged: HandleMetaPropertyChanged);
public static readonly BindableProperty VisibleProperty = BindableProperty.Create(nameof(Visible), typeof(bool), typeof(Button), true);
public string Text { get => (string)GetValue(TextProperty); set => SetValue(TextProperty, value); }
public string Meta { get => (string)GetValue(MetaProperty); set => SetValue(MetaProperty, value); }
public Command TapCommand { get => (Command)GetValue(TapCommandProperty); set => SetValue(TapCommandProperty, value); }
public object TapCommandParam { get => (object)GetValue(TapCommandParamProperty); set => SetValue(TapCommandParamProperty, value); }
public bool Selected { get => (bool)GetValue(SelectedProperty); set => SetValue(SelectedProperty, value); }
public bool Visible { get => (bool)GetValue(VisibleProperty); set => SetValue(VisibleProperty, value); }
string b;
string s;
string f;
public Button()
{
InitializeComponent();
HasShadow = false;
HorizontalOptions = LayoutOptions.Center;
VerticalOptions = LayoutOptions.Center;
}
private static void HandleMetaPropertyChanged(BindableObject bindable, object oldValue, object meta)
{
var control = (Button)bindable;
if (control != null) {
control.b = ((string)meta).Substring(0, 1); // background surface
control.s = ((string)meta).Substring(1, 1); // shape
control.f = ((string)meta).Substring(2, 1); // font
control.BackgroundColor = control.b == "g" ?
(Color)Application.Current.Resources["GridButtonBackgroundColor"] :
(Color)Application.Current.Resources["PageButtonBackgroundColor"];
control.BorderColor = control.b == "g" ?
(Color)Application.Current.Resources["GridButtonBorderColor"] :
(Color)Application.Current.Resources["PageButtonBorderColor"];
if (control.s == "s")
{
control.CornerRadius = 5;
control.Padding = new Thickness(10, 5);
}
else
{
control.WidthRequest = 50;
control.HeightRequest = 50;
control.CornerRadius = 25;
control.Padding = new Thickness(0);
}
Application.Current.Resources.TryGetValue("TextButtonsLabelRes", out object textRes);
Application.Current.Resources.TryGetValue("IconButtonsLabelRes", out object iconRes);
control.ButtonLabel.Style = (control.f == "t") ?
(Style)textRes :
(Style)iconRes;
}
}
private static void HandleSelectedPropertyChanged(BindableObject bindable, object oldValue, object selected)
{
var control = (Button)bindable;
if (control != null)
control.ButtonLabel.TextColor =
((bool)selected) ?
(control.b == "g" ?
(Color)Application.Current.Resources["GridEButtonTextColor"] :
(Color)Application.Current.Resources["PageEButtonTextColor"]) :
(control.b == "g" ?
(Color)Application.Current.Resources["GridButtonTextColor"] :
(Color)Application.Current.Resources["PageButtonTextColor"]);
}
private async void ChangeTheColours(Object sender, EventArgs e)
{
if ((string)this.ButtonLabel.Text.Substring(0, 1) != " ")
{
BackgroundColor = this.b == "g" ?
(Color)Application.Current.Resources["GridCButtonBackgroundColor"] :
(Color)Application.Current.Resources["PageCButtonBackgroundColor"];
BorderColor = this.b == "g" ?
(Color)Application.Current.Resources["GridCButtonBorderColor"] :
(Color)Application.Current.Resources["PageCButtonBorderColor"];
await Task.Delay(500);
BackgroundColor = this.b == "g" ?
(Color)Application.Current.Resources["GridButtonBackgroundColor"] :
(Color)Application.Current.Resources["PageButtonBackgroundColor"];
BorderColor = this.b == "g" ?
(Color)Application.Current.Resources["GridButtonBorderColor"] :
(Color)Application.Current.Resources["PageButtonBorderColor"];
}
}
}
}
For reference here's the SetProperty function:
public class ObservableObject : INotifyPropertyChanged
{
protected virtual bool SetProperty<T>(
ref T backingStore, T value,
[CallerMemberName]string propertyName = "",
Action onChanged = null)
{
if (EqualityComparer<T>.Default.Equals(backingStore, value))
return false;
backingStore = value;
onChanged?.Invoke();
OnPropertyChanged(propertyName);
return true;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = "") =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Just as a reminder the code and method here is working good except for the initial set up where it does not seem to call the propertyChanged event as expected.
The default value on your SelectedProperty is false, OnPropertyChanged is only fired when the current value differs from the new value. Hence only the 3rd button, whose value is being set to true is being fired.
You have to define your initial state of the control in its constructor. Upon any property changes the subsequent changes will be handled in the PropertyChanged handler

Removing an item from a ListBox with MVVM

I'm trying to remove an item from a ListBox. The command is properly fired and the item is correctly removed from the database but the list is not refreshed.
Here is the ViewModel. I'm using MVVM Light 4.1
public class ViewAllViewModel : ViewModelBase
{
public ViewAllViewModel()
{
NavigateToAddNew = new RelayCommand(() => NavigationController<Views>.Current.NavigateTo(Views.AddNew));
Remove = new RelayCommand<int>(DeleteMeasure);
using (var repository = App.ServiceLocator.Get<IRepository>())
{
Measures = new ObservableCollection<Measure>(repository.Measures);
}
}
private void DeleteMeasure(int measureId)
{
Measure measure;
using (IRepository repository = App.ServiceLocator.Get<IRepository>())
{
measure = repository.Measures.Single(m => m.Id == measureId);
repository.Measures.Delete(measure);
repository.SaveChanges();
}
measure = Measures.Single(m => m.Id == measureId);
if (Measures.Remove(measure))
{
RaisePropertyChanged(() => Measures);
}
}
public RelayCommand NavigateToAddNew { get; set; }
public RelayCommand<int> Remove { get; set; }
private ObservableCollection<Measure> _measures;
public ObservableCollection<Measure> Measures
{
get { return _measures; }
set { Set(() => Measures, ref _measures, value); }
}
}
Thanks for the help.
PS: I know there are similar questions but none of the accepted answers worked for me :(
EDIT 1
This is the code I use in the XAML page to bind the ListBox to the list of items:
<ListBox Grid.Row="1" DataContext="{Binding Path=Measures}" ItemsSource="{Binding}" />
here is the binding of the ViewModel to the main container
<Grid DataContext="{Binding Source={StaticResource Locator}, Path=ViewAll}" x:Name="LayoutRoot" />
EDIT 2
This is the full code of the ViewModel
public class ViewAllViewModel : ViewModelBase
{
public ViewAllViewModel()
{
NavigateToAddNew = new RelayCommand(() => NavigationController<Views>.Current.NavigateTo(Views.AddNew));
Remove = new RelayCommand<int>(DeleteMeasure);
LoadMeasures();
Messenger.Default.Register<PropertyChangedMessage<ObservableCollection<Measure>>>(this, message => LoadMeasures());
}
private void LoadMeasures()
{
using (var repository = App.ServiceLocator.Get<IRepository>())
{
Measures = new ObservableCollection<Measure>(repository.Measures.OrderByDescending(m => m.MeasureDate).ThenByDescending(m => m.Id).Take(20));
}
}
private void DeleteMeasure(int measureId)
{
Measure measure;
using (IRepository repository = App.ServiceLocator.Get<IRepository>())
{
measure = repository.Measures.Single(m => m.Id == measureId);
repository.Measures.Delete(measure);
repository.SaveChanges();
}
measure = Measures.Single(m => m.Id == measureId);
Measures.Remove(measure);
RaisePropertyChanged("LastMeasure", null, measure, true);
}
public RelayCommand NavigateToAddNew { get; set; }
public RelayCommand<int> Remove { get; set; }
private ObservableCollection<Measure> _measures;
public ObservableCollection<Measure> Measures
{
get { return _measures; }
set { Set(() => Measures, ref _measures, value); }
}
}
I don't see anything obviously wrong. All I can suggest is to try simplifying your ListBox to just this:
<ListBox Grid.Row="1" ItemsSource="{Binding Path=Measures}" />
And remove the code that calls RaisePropertyChanged(() => Measures); (since it should not be needed).
If neither of those work, I would test to see what happens if you completely reset your Measures property, as in:
private void DeleteMeasure(int measureId)
{
using (IRepository repository = App.ServiceLocator.Get<IRepository>())
{
var measure = repository.Measures.Single(m => m.Id == measureId);
repository.Measures.Delete(measure);
repository.SaveChanges();
}
Measures = repository.Measures;
}
If that causes a successful refresh of the ListBox, it would imply that something is going on with the ObservableCollection.

Resources