I apologize in advance for my poor spelling due to my reliance on google translate
When sending an image in 64 format between Xamarin shell pages, the property is not affected in the recipient ViewModel
Here is the query sending code
await Shell.Current.GoToAsync(
state: $"{nameof(SuppliersByProdectID)}?" +
$"{nameof(Suppliers.SuppliersByProdectID.ItemId)}={item.AIItemId}" + "&" +
//
//Image property is string here
//
$"{nameof(Suppliers.SuppliersByProdectID.Image)}={item.AIItemImage}");
Here is the Query receipt code
[QueryProperty(nameof(ItemId), nameof(ItemId))]
[QueryProperty(nameof(Image), nameof(Image))]
public class SuppliersByProdectID : MyBaseViewModel
{
private string image;
public string Image
{
get => image;
set
{
//I set a breakpoint here, but the program does not stop
//at it
SetProperty(ref image, value);
ItemImage = ImagesConverter.GetImageSource(value);
}
}
private ImageSource itemImage;
public ImageSource ItemImage
{
get => itemImage;
set => SetProperty(ref itemImage, value);
}
private string itemId;
public string ItemId
{
get => itemId;
set
{
SetProperty(ref itemId, value);
FillSuppliers();
}
}
}
Execution does not pass the Image property, although it carries a value in the ViewModel dispatch, and this is when the execution is stopped with a breakpoint
Related
I have been trying to figure out why my app is consuming so much memory, I have a searchbar that is used to filter by name property of my ObservableCollection and when the search contents change a lot from "Test" to a blank string "" the memory continually increases. I know not everyone will be changing the search field 1000x times but that memory never gets freed up and there must be a reason? I've been looking at the memory usage in the emulator Pixel 5 under running services in the dev options -- I'm using Visual studio community 2022 preview 6.
The top image is before using search and the bottom is after 500X times changing searchbar text from "Test" to "" (I used a loop 500X)
Here is the code:
async void Item_Search(object sender, TextChangedEventArgs e)
{
var currentSearchText = e.NewTextValue.ToLowerInvariant();
if (string.IsNullOrWhiteSpace(currentSearchText))
{
currentSearchText = string.Empty;
}
var filteredItems = ItemListSource.Where(value => value.Name.ToLowerInvariant().Contains(currentSearchText)).ToList();
foreach (var value in ItemListSource)
{
if (!filteredItems.Contains(value))
{
ItemList.Remove(value);
}
else if (!ItemList.Contains(value))
{
ItemList.Add(value);
}
}
}
The itemlistsource is an observablecollection that is set in onappearing from a database pull -- await App.Database.GetItems(); ItemList is the ObservableCollection that gets displayed which is initially set equal to the itemlistsource also in onappearing.
The model is:
public class Item
{
[PrimaryKey, AutoIncrement]
public int id { get; set; }
private string name;
public string Name { get { return name; } set { name = value; } }
private string notes;
public string Notes { get { return notes; } set { notes = value; } }
private string category;
public string Category { get { return category; } set { category = value; } }
}
}
I'm working on a Xamarin Forms project. I have a very simple page where a job seeker searches job posts by entering a location(ex. New York) and a radius(picker). I have the following code in my PageViewModel:
public class SearchPageViewModel : BaseViewModel
{
private readonly INavigation _navigation;
private readonly GooglePlacesApiService _api;
private IEnumerable<JobPost> Posts;
public int RadiusIndex {get;set;}
public SearchPageViewModel (INavigation navigation, IEnumerable<JobPost> posts)
{
_navigation = navigation;
var settings = GoogleApiSettings.Builder.WithApiKey("MY_API_KEY").Build();
_api = new GooglePlacesApiService(settings);
this.posts =posts;
}
public ICommand DoSearchCommand
=> new Command(async () => await DoSearchAsync().ConfigureAwait(false), () => CanSearch);
public ICommand SelectItemCommand
=> new Command<Prediction>(async (prediction) =>
{
//!!!Filter jobposts based on position and radius to be able to display it!!!
_api.ResetSessionToken();
});
private string _searchText;
public string SearchText
{
get => _searchText;
set
{
if (SetProperty(ref _searchText, value))
OnPropertyChanged("CanSearch");
}
}
private List<Prediction> _results;
public List<Prediction> Results
{
get => _results;
set => SetProperty(ref _results, value);
}
public bool CanSearch => !string.IsNullOrWhiteSpace(SearchText) && SearchText.Length > 2;
private async Task DoSearchAsync()
{
var results = await _api.GetPredictionsAsync(SearchText)
.ConfigureAwait(false);
if(results != null && results.Status.Equals("OK"))
{
ResultCount = results.Items.Count;
Results = results.Items;
OnPropertyChanged("HasResults");
}
}
}
public class JobPost
{
public Position Location {get;set;}
}
Ok, so far it works well. Once the search button is clicked the job seeker get a list of predictions and picks a place. But the problem is now, how do I get the positioning(longitude and latitude) of the picked prediction, so I can filter the job posts so I can dsiplay the result to the job seeker.
Thanks in advance.
use the Essentials GeoCoding plugin
var address = "New York";
var locations = await Geocoding.GetLocationsAsync(address);
locations will be a list of potential matches, each with a lat/long
var location = locations?.FirstOrDefault();
if (location != null)
{
Console.WriteLine($"Latitude: {location.Latitude}, Longitude: {location.Longitude}, Altitude: {location.Altitude}");
}
My aim is to create a custom control displaying some images, which can be added/exchanged by the user of that control. So, if it is added to a Form, the GUI designer should be able to change some or all images provided by the control editing the appropriate attribute.
In my Test-Project I have a simple control with 4 Attributes:
public Image MyImage { get; set; } = null;
public List<int> MyListOfInt { get; set; } = new List<int>();
public List<Image> MyListOfImages { get; set; } = new List<Image>();
public ImageList MyImageList { get; set; } = new ImageList();
Using this control in a Windows Form Project, clicking on
MyImage brings up the 'Select resource' dialog. OK
MyListOfInt brings up the 'Int32 Collection Editor' dialog. OK
MyListOfImages brings up the 'Image Collection Editor' dialog, but using 'Add' button shows message:
'Cannot create an instance of System.Drawing.Image because it is an
abstract class.'
MyImageList shows an emtpy list, which cannot be edited.
My question is, if it's possible to tell VS Designer to use the 'Select resource' dialog when clicking 'Add' button and what needs to be done?
Starting from Marwie's comment, I was able to solve the problem.
There are three requirements that a collection should meet in order to be successfully persisted with the CollectionEditor:
The collection must implement the IList interface (inheriting from System.Collections.CollectionBase is in most of the cases the best option).
The collection must have an Indexer property.
The collection class must implement one or both of the following methods: Add and/or AddRange
So I created a class 'ImageItem' containing
an image
[Category("ImageItem")]
[DefaultValue(typeof(Image), null)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public Image Picture {
get { return m_Picture; }
set { m_Picture = value; }
}
a name (optional)
[Category("ImageItem")]
[DefaultValue(typeof(string), "")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public string Name {
get { return m_Name; }
set { m_Name = value; }
}
a value (optional)
[Category("ImageItem")]
[DefaultValue(typeof(int), "-1")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public int Value {
get { return m_Value; }
set { m_Value = value; }
}
and a collection 'ImageCollection' holding instances of this class according to the conditions mentioned above:
public class ImageCollection : CollectionBase
public ImageItem this[int i]
public ImageItem Add(ImageItem item)
Then I created a control containing only this collection, initialized with one image:
public partial class MyControl: UserControl
{
public MyControl() {
InitializeComponent();
}
private ImageCollection m_MyImageCollection = new ImageCollection()
{ new ImageItem(0, "Failure", Properties.Resources.Cross), new ImageItem(1, "OK", Properties.Resources.Tickmark) };
[Browsable(true), Category("A Test"), DisplayName("Image Collection (ImageCollection)"), Description("Edit image collection")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[Editor(typeof(System.ComponentModel.Design.CollectionEditor), typeof(System.Drawing.Design.UITypeEditor))]
public ImageCollection MyImageCollection {
get { return m_MyImageCollection; }
}
}
After compiling this code the designer shows that property. Now it is possible to add images using the common designer GUI controls.
I tried to change the default images compiled into this control when using it on my form, but I recognized, that the designer cannot remove content. It only stores the 'Add' action. So I modified the code to search within the collection for another item with the same ID. If there is one available, that instance is removed and replaced with the new one. Therefore I had to implement the AddRange method too.
public ImageItem Add(ImageItem item) {
for(int i = 0; i < InnerList.Count; i++) {
if(InnerList[i] is ImageItem) {
if(((ImageItem)InnerList[i]).Value == item.Value) {
InnerList.RemoveAt(i);
}
}
}
this.InnerList.Add(item);
return item;
}
public void AddRange(ImageItem[] array) {
foreach(ImageItem item in array) {
Add(item);
}
}
So my final classes are:
public class ImageItem {
private int m_Value = -1;
private string m_Name = "ImageItem";
private Image m_Picture = null;
[Category("ImageItem")]
[DefaultValue(typeof(int), "-1")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public int Value {
get { return m_Value; }
set { m_Value = value; }
}
[Category("ImageItem")]
[DefaultValue(typeof(string), "")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public string Name {
get { return m_Name; }
set { m_Name = value; }
}
[Category("ImageItem")]
[DefaultValue(typeof(Image), null)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public Image Picture {
get { return m_Picture; }
set { m_Picture = value; }
}
public ImageItem() { }
public ImageItem(int value, string name, Image image) {
this.m_Value = value;
this.m_Name = name;
this.m_Picture = image;
}
}
And ImageCollection:
public class ImageCollection : CollectionBase {
public ImageCollection() {}
public ImageItem this[int i]
{
get { return (ImageItem)this.InnerList[i]; }
set { this.InnerList[i] = value; }
}
public ImageItem Add(ImageItem item) {
for(int i = 0; i < InnerList.Count; i++) {
if(InnerList[i] is ImageItem) {
if(((ImageItem)InnerList[i]).Value == item.Value) {
InnerList.RemoveAt(i);
}
}
}
this.InnerList.Add(item);
return item;
}
public void AddRange(ImageItem[] array) {
foreach(ImageItem item in array) {
Add(item);
}
}
public void Remove(ImageItem item) {
this.InnerList.Remove(item);
}
public bool Contains(ImageItem item) {
return this.InnerList.Contains(item);
}
public ImageItem[] GetValues() {
ImageItem[] item= new ImageItem[this.InnerList.Count];
this.InnerList.CopyTo(0, item, 0, this.InnerList.Count);
return item;
}
protected override void OnInsert(int index, object value) {
base.OnInsert(index, value);
}
}
I've got another answer from MSDN:
How to edit UserControl attribute of type ImageList in Designer PropertyGrid (add/remove/exchange images)
I will describe the idea in short. First create a new control with an ImageList attribute.
public partial class NewControl : UserControl {
public NewControl() {
InitializeComponent();
}
public ImageList MyImageList { get; set; } = null;
}
Then drag this control on any form.
Additionally drag an ImageList control from Toolbox onto this
form - I called it 'MyImages'.
Edit MyImages → Images with designer.
Assign 'MyImages' to NewControl's instance attribute MyImageList in property grid
The only drawback I see here is, that if the control already has an initialized ImageList attribute, the designer cannot handle it. If you try to edit MyImageList before you assigned another list, the designer shows the controls default list, that comes with the control. But it's not possible to edit that list.
This solution is much easier to deal with and much shorter than the first solution above, so that I prefer it more.
I have mvvmcross project, a ToggleButton with bindings for Checked, TextOn, and TextOff properties. I set the texts for those programmatically, I see in the setter that RaisePropertyChanged() is called, but the button text in UI stays the same, unless I click on it or change value of the property bound to "Checked". Changing Checked seems like a workaround, but ugly, is there a proper way?
FirstView.axml contains
<ToggleButton
android:id="#+id/myBtn"
local:MvxBind="TextOff MyBtnOFFLabel; TextOn MyBtnONLabel; Checked MyBtnChecked"
android:textOff="OFF"
android:textOn="ON"
android:checked="true"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_weight="1"
android:width="0dp" />
FirstViewModel.cs contains
private string myBtnOFFLabel;
public string MyBtnOFFLabel
{
get { return myBtnOFFLabel; }
set { myBtnOFFLabel = value; RaisePropertyChanged(() => MyBtnOFFLabel); }
}
private string myBtnONLabel;
public string MyBtnONLabel
{
get { return myBtnONLabel; }
set { myBtnONLabel = value; RaisePropertyChanged(() => MyBtnONLabel); }
}
public FirstViewModel()
{
Global.EventSomethingChanged += Handler_SomethingChanged;
}
void Handler_SomethingChanged(object sender, EventArgs e)
{
UpdateUI();
}
private void UpdateUI()
{
MyBtnOFFLabel = "new OFF";
MyBtnONLabel = "new ON";
}
SecondViewModel.cs contains
Global.FireEventSomethingChanged();
as expected, UpdateUI() is called in FirstViewModel.cs and it updates the label properties for myBtn (confirmed in debug mode), but when I close SecondViewModel in emulator I see the old label remaining in first view UI. If I click on that button, it switches to showing correct labels.
I believe you needed to RaisePropertyChanged on your MyBtnChecked. In your comment above you mention that adding it to UpdateUI() causes it to redraw properly. That's not a "work around" per se as you need to put that somewhere. You could also do this:
private string myBtnOFFLabel;
public string MyBtnOFFLabel
{
get { return myBtnOFFLabel; }
set {
myBtnOFFLabel = value;
RaisePropertyChanged(() => MyBtnOFFLabel);
RaisePropertyChanged(() => MyBtnChecked);
}
}
private string myBtnONLabel;
public string MyBtnONLabel
{
get { return myBtnONLabel; }
set {
myBtnONLabel = value;
RaisePropertyChanged(() => MyBtnONLabel);
RaisePropertyChanged(() => MyBtnChecked);
}
}
As it sounds like your MyBtnChecked is dependent on changes to MyBtnOFFLabel and MyBtnONLabel. Otherwise you need to be firing this RaisePropertyChanged somewhere else.. For example I would have expected to see a property in your example code for MyBtnChecked which would do this. Then in your view model when MyBtnChecked is changed it would fire the RaisePropertyChanged event to mark it as checked or not when your ViewModel bool property is changed.
I've seen question ReactiveUI: Using CanExecute with a ReactiveCommand, however my issue is that I have a string property, UniqueID, and I want it to only execute when it has a length equal to 7. I cannot seem to come up with an observer that doesn't crash the program. What is the correct simple way to do this?
public class MainViewModel : ReactiveValidatedObject
{
public MainViewModel()
{
RetrieveRecord = new ReactiveAsyncCommand(/* what goes here for CanExecute */);
RetrieveRecord.Subscriber(x => Record = new Record(UniqueId));
// or do we use the method RetrieveRecord.CanExecute()?
// the next line crashes the app
RetrieveRecord.CanExecute(UniqueId.Length == 7);
}
public ReactiveAsyncCommand RetrieveRecord { get; private set; }
string _uniqueId;
public string UniqueId
{
get { return _uniqueId; }
set
{
_clientId = value;
this.RaisePropertyChanged(x => x.UniqueId);
}
}
}
How about:
RetrieveRecord = new ReactiveAsyncCommand(
this.WhenAny(x => x.UniqueId, x => x.Value.Length == 7));