Property binding in MVVM - c#-4.0

my cpde in ModelView :
public Boolean EnableTextBox { get; set; }
public CustomerAccountVM()
{
this.EnableTextBox = false;
//...
}
code in View:
XAML :
<TextBox Text="{Binding Path=IdCustomer, Mode=Default}" IsEnabled="{Binding Path=EnableTextBox,Mode=Default}" />
Why the code does not work?
no answer ?

You're not publishing the fact that the Enable property has been updated.
You need to implement the INotifyPropertyChanged interface and change your property to be:
private Boolean _enableTextBox;
public Boolean EnableTextBox
{
get { return _enableTextBox; }
set
{
_enableTextBox = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
You should wrap the PropertyChanged code in a method so you're not repeating yourself.

Related

Xamarin QLPreviewController + NavigationPage broken on iOS 10

After updating the device to iOS 10, QLPreviewController stopped to display correctly the documents. It shows the white screen.
I have extracted the sample scenario from the app.
It contains single page with two buttons that should load two different documents:
<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:QuickLookIOS10Test"
x:Class="QuickLookIOS10Test.QuickLookIOS10TestPage">
<StackLayout Orientation="Vertical">
<Button Text="Load first doc" Clicked="OnLoadFirstClicked"/>
<Button Text="Load second doc" Clicked="OnLoadSecondClicked"/>
<Button Text="Navigate forward" Clicked="OnForwardClicked"/>
<local:QLDocumentView
x:Name="DocumentView"
BackgroundColor="Silver"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"/>
</StackLayout>
</ContentPage>
where:
public class QLDocumentView : View
{
public static readonly BindableProperty FilePathProperty =
BindableProperty.Create(nameof(FilePath), typeof(string), typeof(QLDocumentView), null);
public string FilePath
{
get { return (string)GetValue(FilePathProperty); }
set { SetValue(FilePathProperty, value); }
}
}
There is a custom renderer involved:
public class QLDocumentViewRenderer : ViewRenderer<QLDocumentView, UIView>
{
private QLPreviewController controller;
public override SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint)
{
//This is a fix to prevent incorrect scaling after rotating from portrait to landscape.
//No idea why does this work :( Bug #101639
return new SizeRequest(Size.Zero, Size.Zero);
}
protected override void OnElementChanged(ElementChangedEventArgs<QLDocumentView> e)
{
base.OnElementChanged(e);
if (Control == null)
{
controller = new QLPreviewController();
SetNativeControl(controller.View);
}
RefreshView();
}
protected override void OnElementPropertyChanged(object sender,
System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == QLDocumentView.FilePathProperty.PropertyName)
{
RefreshView();
}
}
private void RefreshView()
{
DisposeDataSource();
if (Element?.FilePath != null)
{
controller.DataSource = new DocumentQLPreviewControllerDataSource(Element.FilePath);
}
controller.ReloadData();
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
DisposeDataSource();
DisposeController();
}
}
private void DisposeDataSource()
{
var dataSource = controller.DataSource;
controller.DataSource = null;
dataSource?.Dispose();
}
private void DisposeController()
{
controller?.Dispose();
controller = null;
}
private class DocumentQLPreviewControllerDataSource : QLPreviewControllerDataSource
{
private readonly string fileName;
public DocumentQLPreviewControllerDataSource(string fileName)
{
this.fileName = fileName;
}
public override nint PreviewItemCount(QLPreviewController controller)
{
return 1;
}
public override IQLPreviewItem GetPreviewItem(QLPreviewController controller, nint index)
{
NSUrl url = NSUrl.FromFilename(fileName);
return new QlItem(url);
}
private sealed class QlItem : QLPreviewItem
{
private readonly NSUrl itemUrl;
public QlItem(NSUrl uri)
{
itemUrl = uri;
}
public override string ItemTitle { get { return string.Empty; } }
public override NSUrl ItemUrl { get { return itemUrl; } }
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
this.itemUrl?.Dispose();
}
}
}
}
}
If the application setups the main page like below:
MainPage = new NavigationPage(new QuickLookIOS10TestPage());
it does work on iOS 9.3 but not on iOS 10. If I remove NavigationPage:
MainPage = new QuickLookIOS10TestPage();
it works on both iOS versions.
The code behind for button clicks just sets the FilePath property of the control.
Sample app demonstrating the problem
Xamarin Forms 2.3.2.127
Xamarin Studio 6.1.1 (build 15)
I've faced with the same problem. It looks like something was changed or even broken in QuickLook in iOS10, but the solution is quite simple:
public class PdfViewerControlRenderer : ViewRenderer<PdfViewerControl, UIView>
{
private readonly bool IsOniOS10;
private UIViewController _controller;
private QLPreviewController _qlPreviewController;
public PdfViewerControlRenderer()
{
IsOniOS10 = UIDevice.CurrentDevice.CheckSystemVersion(10, 0);
}
protected override void OnElementChanged(ElementChangedEventArgs<PdfViewerControl> e)
{
if (e.NewElement != null)
{
_controller = new UIViewController();
_qlPreviewController = new QLPreviewController();
//...
// Set QuickLook datasource here
//...
if (!IsOniOS10)
{
_controller.AddChildViewController(_qlPreviewController);
_controller.View.AddSubview(_qlPreviewController.View);
_qlPreviewController.DidMoveToParentViewController(_controller);
}
SetNativeControl(_controller.View);
}
}
public override void LayoutSubviews()
{
base.LayoutSubviews();
_controller.View.Frame = Bounds;
_controller.View.AutoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight;
_qlPreviewController.View.Frame = Bounds;
if (IsOniOS10)
{
_controller.View.AddSubview(_qlPreviewController.View);
_qlPreviewController.DidMoveToParentViewController(_controller);
}
}
}
Result:

Updating a ListView with Xamarin.Forms

I am having an issue with list views on in a couple of my Xamarin Forms applications. One form is within a tabbed page setup, the other is a normal content page (different apps)
I have a class like this
public class SomeClass
{
public string StringOne {get;set;}
public string StringTwo {get;set;}
public int IntOne {get;set;}
}
In my Content page, I set up an ObservableCollection and add some data in. I then tell the list that SomeClass is my ItemSource. This produces the ListView correctly on all of my devices.
The problem is that when I change one of the properties, nothing on the ListView changes (so if say I have 3 objects in the Observable and remove one, the list still says 3 - or if I change a property in my second object, the second item on the ListView doesn't change either).
I have also tried to solve the problem by using a standard List and implement INotifyChanged within the class. Again though, the ListView doesn't alter when the List changes.
I know the data has altered as if I make a change to the object, come out and go back in, the data has changed in the UI.
Am I doing something wrong or is this a bug I need to putting into Bugzilla?
It will not change if you don't bind it and implement INotifyPropertyChanged interface.
Sample Code:
public class ObservableProperty : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class SomeClass:ObservableProperty
{
string stringOne;
string stringTwo;
int intOne;
public string StringOne
{
get{return stringOne;}
set
{
stringOne = value;
OnPropertyChanged("StringOne");
}
}
public string StringTwo
{
get{ return stringTwo;}
set
{
stringTwo = value;
OnPropertyChanged("StringTwo");
}
}
public int IntOne
{
get{ return intOne;}
set
{
intOne = value;
OnPropertyChanged("IntOne");
}
}
}
public class MainVM:ObservableProperty
{
ObservableCollection<SomeClass> items;
public ObservableCollection<SomeClass> items
{
get{return items;}
set
{
items = value;
OnPropertyChanged("Items");
}
}
public MainVM()
{
Items = new ObservableCollection<SomeClass>();
Items.Add(new SomeClass(){StringOne = "123", StringTwo = "test", IntOne =12});
}
public void CallMeForChangingProperty()
{
SomeClass item = Items[0];
item.StringOne = "Test1";
}
}
public class MainView
{
public MainView()
{
this.BindingContext= new MainVM()
}
}
< ListView ItemsSource="{Binding Items}" RowHeight="120">
< ListView.ItemTemplate>
< DataTemplate>
< ViewCell>
< ViewCell.View>
< StackLayout>
< Label Text= "StringOne" />
< Label Text= "StringTwo" />
< Label Text= "IntOne" />
</ StackLayout>
</ ViewCell.View>
</ ViewCell>
</ DataTemplate>
</ ListView.ItemTemplate>
</ ListView>
Answer given by #eakgul works like a charm for me.
I'll attach here what I've implemented, maybe it could help someone.
You have to set INotifyPropertyChanged both, to the ObservableColection and to it's itens.
I have a BaseViewModel with INotifyPropertyChanged as follows:
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
protected void SetProperty<T>(ref T backingField, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals( backingField, value)) return;
backingField = value;
OnPropertyChanged(propertyName);
}
}
On my BluetoothPage.xaml, first I set bindincontext to my BluetoothPageViewModel.cs and set the ListView ItemsSource and it's binded labels:
<ContentPage.BindingContext>
<viewmodel:BluetoothPageViewModel/>
</ContentPage.BindingContext>
<ContentPage.Content>
<StackLayout Padding="5,10">
<Button x:Name="Scan_Devices_Button"
Command="{Binding SearchNew_Button_Clicked}"/>
<ListView x:Name="DevicesList"
ItemsSource="{Binding BluetoothDevices}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
IsPullToRefreshEnabled="True">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0"
Text="{Binding device.Device.NativeDevice.Name}"/>
<Label Grid.Column="1"
Text="{Binding device.Device.NativeDevice.Address, StringFormat='ID: {0}'}"/>
</Grid>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0"
Text="{Binding device.Rssi, StringFormat='Power: {0:F2}dbm'}"/>
<Label Grid.Column="1"
Text="{Binding distance, StringFormat='Distance: {0:F2}m'}"/>
</Grid>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage.Content>
Then, in my BluetoothPageViewModel.cs I extend it with the BaseViewModel and declare ItemsSource BluetoothDevices with INotifyPropertyChanged. At this moment, everytime I change an item on the ObservableCollection BluetoothDevices, the ListView will be updated. But, If I made a change on an item inside the ObservableCollection, nothing will happen!
So, you must set INotifyPropertyChanged to it's itens.
Following is my BluetoothPageViewModel, which uses a class BluetoothPageModel in the PageModel BluetoothPageModel.cs
The BluetoothPageViewModel:
public class BluetoothPageViewModel : BaseViewModel
{
public BluetoothPageViewModel()
{
SearchNew_Button_Clicked = new Command(NewDevices_Button_Clicked_Event);
Scan_Devices_Button_BgColor = "#D6D7D7";
Scan_Devices_Button_Text = "Scan nearby devices";
}
#region Declarations
public List<IDevice> iDeviceList = new List<IDevice>();
public ObservableCollection<BluetoothPageModel> _bluetoothDevices = new ObservableCollection<BluetoothPageModel>();
public BluetoothPageModel _selectedItem;
public ObservableCollection<BluetoothPageModel> BluetoothDevices
{
get { return _bluetoothDevices; }
set { SetProperty(ref _bluetoothDevices, value); }
}
public BluetoothPageModel SelectedItem
{
get { return _selectedItem; }
set { SetProperty(ref _selectedItem, value); }
}
public ICommand SearchNew_Button_Clicked { get; private set; }
#endregion
#region Functions
private void NewDevices_Button_Clicked_Event(object obj)
{
// discover some devices
if (!CrossBleAdapter.Current.IsScanning)
{
BluetoothDevices.Clear();
iDeviceList.Clear();
var scanner = CrossBleAdapter.Current.Scan().Subscribe(scanResult =>
{
if (!iDeviceList.Contains(scanResult.Device))
{
iDeviceList.Add(scanResult.Device);
Device.BeginInvokeOnMainThread(() =>
{
BluetoothDevices.Add(new BluetoothPageModel
{
device = scanResult,
distance = Math.Pow(10, ((-68 - scanResult.Rssi) / 31.1474))
});
});
}
else
{
int ind = iDeviceList.IndexOf(scanResult.Device);
Device.BeginInvokeOnMainThread(() =>
{
BluetoothDevices[ind].device = scanResult;
BluetoothDevices[ind].distance = Math.Pow(10, ((-68 - scanResult.Rssi) / 31.1474));
});
}
});
}
else
{
CrossBleAdapter.Current.StopScan(); //When you want to stop scanning
}
}
#endregion
}
Finally, to be able to update data when you change a property of the BluetoothPageModel class:
public class BluetoothPageModel:BaseViewModel
{
public IScanResult _device;
public double _distance;
public IScanResult device
{
get { return _device; }
set { SetProperty(ref _device, value); }
}
public double distance
{
get { return _distance; }
set { SetProperty(ref _distance, value); }
}
}
Thanks to eakgul answer I could get it working. Hope it can help someone else.

Custom bindable control in a MvvmCross Touch project

I have a MvxBaseBindableCollectionViewCell which loads a xib that contains a custom button. I would like to be able to pass this custom button a ViewModel to bind to. Is this possible?
I'm trying to acheive something like MyButton.ViewModel = ViewModel.ChildViewModel and have ViewModel.ChildViewModel.Name show as the button title.
If you want to custom bind a cell, then there's a tutorial on this in http://slodge.blogspot.co.uk/2013/01/uitableviewcell-using-xib-editor.html
If you want to create a fully bindable UIButton within that View then you can do this using some inheritance like:
[Register("MyButton")]
public class MyButton
: UIButton
, IMvxServiceConsumer
{
private IList<IMvxUpdateableBinding> _bindings;
private const string BindingText = "SpecialTitle Customer.Name";
public MyButton()
{
}
public MyButton(IntPtr handle)
: base(handle)
{
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
foreach (var binding in _bindings)
{
binding.Dispose();
}
_bindings.Clear();
}
base.Dispose(disposing);
}
private object _dc;
public object DataContext
{
get { return _dc; }
set
{
_dc = value;
if (_bindings == null)
{
var binder = this.GetService<IMvxBinder>();
_bindings = binder.Bind(_dc, this, BindingText).ToList();
}
else
{
foreach (var binding in _bindings)
{
binding.DataContext = _dc;
}
}
}
}
public string SpecialTitle
{
get { return this.GetTitle(UIControlState.Normal); }
set { this.SetTitle(value, UIControlState.Normal); }
}
}
Aside> MvvmCross v3 "Hot Tuna" will contain some helper classes to make this a bit simpler to do.

WPF label content databinding

I have a label in WPF4 and trying to bind the content to a value from c# class. I have created an ObjectDataProvider but for some reason can't see the content or updates. Can you point me to what I'm doing wrong?
Here is the xaml -
<Grid.Resources>
<local:SummaryData x:Key="mySummaryData"/> </Grid.Resources>
<Label Name ="lbCurrentVerValue" Grid.Row="1" Grid.Column="2" Content="{Binding Path=frameworkVersion, Source={StaticResource mySummaryData}}"/>
<TextBox Name ="lbFromVerValue" Grid.Row="2" Grid.Column="2" Text="{Binding Source={StaticResource mySummaryData}, Path=frameworkVersion}"/>
and here is the c# code-
namespace DBUpgradeUI
{
public partial class DBUpgReadinessCheck : Window
{
public string userConnStr = String.Empty;
public string userFoldPath = String.Empty;
public SummaryData sd = new SummaryData();
public DBUpgReadinessCheck()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
ReadinessCheck(userConnStr, userFoldPath);
}
public void ReadinessCheck(string connectionString, string folderPath)
{
FrmImportUtility frmWork = new FrmImportUtility();
sd.frameworkVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString(); ;
frmWork.CurrentlyProcessingVersion(connectionString, folderPath, ref sd.currentVersion, ref sd.finalVersion);
}
}
public class SummaryData
{
public string currentVersion = "Test";
public string finalVersion = "finalVerTest";
public string frameworkVersion = String.Empty;
}
}
You must implement INotifyPropertyChanged in SummaryData:
public class SummaryData:INotifyPropertyChanged
{
private string currentVersion = "Test";
public string CurrentVersion
{
get { return currentVersion; }
set {
currentVersion = value;
NotifyChanged("CurrentVersion");
}
}
private void NotifyChanged(string p)
{
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs(property));
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
After trying ways to bind properties from a class object without having dependency properties and having no luck, I have done the following and it works like a charm!
xaml -
<Label Name ="lbCurrentVerValue" Grid.Row="1" Grid.Column="2" Content="{Binding Mode=OneWay, ElementName=step2Window,Path=currentVersion}"/>
<Label Name ="lbFromVerValue" Grid.Row="2" Grid.Column="2" Content="{Binding Mode=OneWay, ElementName=step2Window,Path=finalVersion}"/>
and c# code -
public partial class DBUpgReadinessCheck : Window
{
//Values being passed by step1 window
public string userConnStr = String.Empty;
public string userFoldPath = String.Empty;
//Dependency properties
public string currentVersion
{
get { return (String)this.GetValue(CurVersionProperty); }
set { this.SetValue(CurVersionProperty, value); }
}
public static readonly DependencyProperty CurVersionProperty =
DependencyProperty.Register("currentVersion", typeof(String), typeof(DBUpgReadinessCheck), new UIPropertyMetadata("0"));
public String finalVersion
{
get { return (String)GetValue(FinVerProperty); }
set { SetValue(FinVerProperty, value); }
}
public static readonly DependencyProperty FinVerProperty =
DependencyProperty.Register("finalVersion", typeof(String), typeof(DBUpgReadinessCheck), new UIPropertyMetadata("0"));
public string frameworkVersion
{
get { return (String)GetValue(FrameWrkVersionProperty); }
set { SetValue(FrameWrkVersionProperty, value); }
}
public static readonly DependencyProperty FrameWrkVersionProperty =
DependencyProperty.Register("frameworkVersion", typeof(String), typeof(DBUpgReadinessCheck), new UIPropertyMetadata("0"));
public DBUpgReadinessCheck()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
ReadinessCheck(userConnStr, userFoldPath);
}
public void ReadinessCheck(string connectionString, string folderPath)
{
FrmImportUtility frmWork = new FrmImportUtility();
frameworkVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString();
string tempCurVersion = string.Empty;
String tempFinalVersion = string.Empty;
frmWork.CurrentlyProcessingVersion(connectionString, folderPath, ref tempCurVersion, ref tempFinalVersion);
currentVersion = tempCurVersion;
finalVersion = tempFinalVersion;
//Validation
if (finalVersion == frameworkVersion)
{
string strUri2 = String.Format(#"pack://application:,,,/GreenCheck.jpg");
ImgFrmVersion.Source = new BitmapImage(new Uri(strUri2));
yelloExclamation.Visibility = System.Windows.Visibility.Hidden;
errMsg.Visibility = System.Windows.Visibility.Hidden;
succMsg.Visibility = System.Windows.Visibility.Visible;
btRecheckSetUp.Visibility = System.Windows.Visibility.Hidden;
btStartUpgrade.IsEnabled = true;
}
}
This works great. Thanks everyone for your help.

How do I specify a control as a property in my user control in WPF?

My XAML:
<TextBox x:Name="myTextBox" .../>
<MyExtender TargetTextBox=? .../>
My C#:
MyExtender : UserControl
{
public TargetTextBox { get; set; }
}
How do I set the TargetTextBox property in XAML?
You should be able to do:
<TextBox Name="tb">Something</TextBox>
<my:MyExtender TargetTextBox="{Binding ElementName=tb}" />
but this requires TargetTextBox to be a DependencyProperty. Change MyExtender.xaml.cs to:
public partial class MyExtender : System.Windows.Controls.UserControl
{
public MyExtender()
{
InitializeComponent();
}
public static readonly DependencyProperty TargetTextBoxPropery =
DependencyProperty.Register("TargetTextBox", typeof(TextBox), typeof(MyExtender));
public TextBox TargetTextBox
{
get { return (TextBox)GetValue(TargetTextBoxPropery); }
set { SetValue(TargetTextBoxPropery, value); }
}
}
And you should be set.

Resources