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.
Related
I am new to xamarin, i hope someone can help me with this:
I have a sinple page with entry fields and data binding.
I have page A with a listview. When I click on an item, I get redirected to page B which has the form elements.
async void LvData_ItemSelected(object sender, SelectedItemChangedEventArgs e)
{
if (e.SelectedItem != null)
{
var secondPage = new ProfileDataPage();
secondPage.BindingContext = e.SelectedItem;
await Navigation.PushAsync(secondPage);
}
}
This works, and in page B the fields are filled with the right data.
So now I change the value of an entry field. Then I click on the Save Button and I do something like this (profileData = BindingContext object):
profileData.Height = Functions.ToNullableDouble(Height.Text);
profileData.Weight = Functions.ToNullableDouble(Weight.Text);
etc...
Doesn't the BindingContext know somehow that the value of the entry has changed, and I can just send the BindingContext object to my web api for save, update and so on?
Thank you very much.
for example,here is a mode:
class MyData : INotifyPropertyChanged
{
string height;
string weight;
public MyData(string height,string weight)
{
this.height= height;
this.weight= weight;
}
public string Height
{
set
{
if (height!= value)
{
height= value;
OnPropertyChanged("Height");
}
}
get
{
return height;
}
}
public string Weight
{
set
{
if (weight!= value)
{
weight= value;
OnPropertyChanged("Weight");
}
}
get
{
return weight;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
you could refer to Binding Mode
So I tried your solutions, which helped, but only after I have update my VS, all the nuget packages and so on..
came to this idea by this post: Link
I have no idea why this was necessary, but it works now!
Thank you!
I have a XAML view bound to a ViewModel
When the view loads it sets the binding context to be an object (type GAME_TBL)
When the ViewModel loads, I await a message arriving with a unique ID
I then use this to calll an API and return a game object which I cast to replace the one that is bound
However, even tho debug shows me this is all working, and the PropertyChanged fires, the UI never updates
From the View:
<ContentPage.BindingContext>
<viewmodels:VotingViewModel/>
</ContentPage.BindingContext>
.....
<Label Text="{Binding currentGame.GAME_NAME}" />
In the VotingViewModel:
public class VotingViewModel : BaseViewModel
{
GAME_TBL gameSelected;
public GAME_TBL currentGame
{
get {
return _currentGame;
}
set
{
_currentGame = value;
OnPropertyChanged(nameof(_currentGame));
}
}
public VotingViewModel()
{
MessagingCenter.Subscribe<Views.GamesView, string>(this, "Selected Game", (sender, arg) =>
{
gameID = Convert.ToInt32(arg);
currentGame = loadGameInfo(); // this interacts with an API to set object specifics
});
}
}
In the BaseViewModel
protected virtual void OnPropertyChanged(
string propertyName = null)
{
System.Diagnostics.Debug.WriteLine("Debug: Name of property: " + propertyName);
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(propertyName));
}
You need to inherit BaseViewModel in the Model Class of GAME_TBL. And inside the class GAME_TBL, you need to specify each and every property in the following manner:
private string _gAME_NAME;
public string GAME_NAME
{
get => _gAME_NAME;
set
{
_gAME_NAME = value;
OnPropertyChanged(nameof(GAME_NAME));
}
}
Each property should have a an internal backing property, as above.
Ive been using Montemagno's plugin for saving Settings but someone mentioned it's better to user preferences.essential. There's not much tutorial on it.
I'm having problem the getting and setting the value and accessing it from another file from where I set it.
//Settings.cs
public static string NameSettings
{
get
{
return AppSettings.GetValueOrDefault(SettingsnameKey, SettingsDefault);
}
set
{
AppSettings.AddOrUpdateValue(SettingsnameKey, value);
}
}
public static string DrainquantitySettings
{
get
{
return AppSettings.GetValueOrDefault(SettingsdrainxKey, SettingsDefault);
}
set
{
AppSettings.AddOrUpdateValue(SettingsdrainxKey, value);
}
}
//this is how i get it in another file
drainxPicker.SelectedItem = Settings.DrainquantitySettings;
nameEntry.Text = Settings.NameSettings;
//to set it
Settings.NameSettings = username;
Settings.DrainquantitySettings = item;
//to convert to preferences?
public static NameSettings
{
get => Preferences.Get(nameof(NameSettings), username);
set => Preferences.Set(nameof(NameSettings), value);
}
//to use it??
nameEntry.Text = Preferences.Settings.NameSettings;
//to set it??
Preferences.Settings.NameSettings = username;
I just did this very thing
public static bool UploadOnlyOverWifi
{
get => Preferences.Get(nameof(UploadOnlyOverWifi), false);
set => Preferences.Set(nameof(UploadOnlyOverWifi), value);
}
According to Jason's reply, you can use Preferences.Set() to save value ,and using Preferences.Get() to retrieve value from preference.
<StackLayout>
<Entry x:Name="entry1" />
<Button
x:Name="btnsave"
Clicked="Btnsave_Clicked"
Text="Save" />
<Button
x:Name="btnget"
Clicked="Btnget_Clicked"
Text="Get" />
</StackLayout>
private void Btnsave_Clicked(object sender, EventArgs e)
{
Preferences.Set("key1", entry1.Text);
}
private void Btnget_Clicked(object sender, EventArgs e)
{
string value1 = Preferences.Get("key1","");
DisplayAlert("Success", "Your Value is " + value1, "OK");
}
I have a TableView in iOS and, in my ViewModel, I have a property to Selected Item in TableView, but I don't know how to bind the Selected Item for this property. How can I do that? My project is cross-platform. I have an Android project and an iOS project. In Android project, I did the bind:
<Mvx.MvxListView
android:id="#+id/lstViewTasks"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:clickable="true"
android:focusableInTouchMode="true"
android:choiceMode="multipleChoice"
local:MvxBind="ItemsSource Tasks; SelectedItem SelectedTask; ItemClick ShowTaskCommand"
local:MvxItemTemplate="#layout/projectmytasksitem" />
but I can't do a equivalent bind in iOS.
That's my TableViewController:
[Register("ProjectMyTasksViewc")]
public class ProjectMyTasksViews : MvxTableViewController<ProjectMyTasksViewModel>
{
//other things
var source = new MvxSimpleTableViewSource(TableView, ProjectMyTasksItem.Key, ProjectMyTasksItem.Key);
TableView.Source = source;
this.CreateBinding(source).To<ProjectMyTasksViewModel>(viewModel => viewModel.Tasks).Apply();
this.CreateBinding(source).For(s => s.SelectedItem).To<ProjectMyTasksViewModel>(viewModel => viewModel.SelectedTask).Apply();
this.CreateBinding(source).For(tableSource => tableSource.SelectionChangedCommand).To<ProjectMyTasksViewModel>(viewModel => viewModel.ShowTaskCommand).Apply();
}
Here is my ViewModel:
public class ProjectMyTasksViewModel : MvxViewModel
{
public Action ShowTaskCommandAction { get; set; }
private IList<Task> _tasks;
public IList<Task> Tasks
{
get { return _tasks; }
set { _tasks = value; RaisePropertyChanged(() => Tasks); }
}
private Task _selectedTask;
public Task SelectedTask
{
get { return _selectedTask; }
set { _selectedTask = value; RaisePropertyChanged(() => SelectedTask); }
}
private MvxCommand _showTaskCommand;
public MvxCommand ShowTaskCommand
{
get
{
_showTaskCommand = _showTaskCommand ?? (_showTaskCommand = new MvxCommand(ExecuteShowTaskCommand));
return _showTaskCommand;
}
}
private void ExecuteShowTaskCommand()
{
if (!SelectedTask.IsCompleted)
{
ShowTaskCommandAction?.Invoke();
}
}
}
I believe it has to do with the timing of your ShowTaskCommand getting executed vs the set of SelectedTask. So if you commented out the code inside ExecuteShowTaskCommand and place a breakpoint inside ExecuteShowTaskCommand as well as the set of SelectedTask you would find that the ExecuteShowTaskCommand is running first and then the set of the SelectedTask.
Alternative implementation
To avoid the timing issue you can instead pass the selected task into your command as a parameter.
MvxCommand<Task> _showTaskCommand;
public MvxCommand<Task> ShowTaskCommand =>
_showTaskCommand ?? (_showTaskCommand = new MvxCommand<Task>(ExecuteShowTaskCommand));
private void ExecuteShowTaskCommand(Task selectedTask)
{
if (!selectedTask.IsCompleted)
{
ShowTaskCommandAction?.Invoke();
}
}
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.