I am new to two way binding in wp7.The below code does not assign the textbox value to the object automatically and returns null.
Xaml:
<Grid x:Name="ContentPanel" DataContext="{Binding usd}" Grid.Row="1" Margin="14,10,10,-10" >
<TextBox Text="{Binding UserName,Mode=TwoWay}" Name="txt1" Width="200" Height="60" FontSize="20" Margin="128,48,128,499"/>
<TextBox Text="{Binding Password,Mode=TwoWay}" Name="txt2" Width="200" Height="60" FontSize="20" Margin="128,263,128,284"/>
<TextBox Text="{Binding Email,Mode=TwoWay}" Name="txt3" Width="200" Height="60" FontSize="20" Margin="128,159,128,388"/>
<Button Content="Send" FontSize="18" Margin="179,413,170,129"
Click="Button_Click_1" />
</Grid>
Cs:
public class UserLogin:INotifyPropertyChanged
{
private string _username;
private string _pwd;
private string _email;
public string UserName
{
get
{
return _username;
}
set
{
_username = value;
OnPropertyChanged("UserName");
}
}
public string Password
{
get
{
return _pwd;
}
set
{
_pwd = value;
OnPropertyChanged("Password");
}
}
public string Email
{
get
{
return _email;
}
set
{
_email = value;
OnPropertyChanged("Email");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
Instantiation:
public UserLogin usd = null;
In constructor:
usd = new UserLogin();
In Button ClickEvent:
private void Button_Click_1(object sender, RoutedEventArgs e)
{
// ContentPanel.DataContext = usd;
MessageBox.Show(usd.Email);
}
Null reference exception in Message box statement. Thanks..
you can just bind to public properties - so your: DataContext="{Binding usd}" should be wrong because usd is just a field
btw if you set this in your ctor too, an remove the xaml binding it could work
usd = new UserLogin();
ContentPanel.DataContext = usd;
About your control/page (which XAML belongs to it)
it's datacontext should contain a usd property
that property should also notifying property!
of course your control/page's datacontext class also should implement INotifyPropertyChanged
because your usd is not set as a property its just a variable.... do one thing
public UserLogin usd {get;set;}
usd = null;
Related
I do not load a small data from the API, in C# code, they are loaded in advance and everything seems to be fine, but as soon as I open the page where ItemsSource = "{Binding BigData}", my UI is blocked for 10 seconds.
Are there any ideas to open the page first, then start loading data without blocking the UI?
I would to suggest you can kick off a task in your view models constructor that loads the data. Using Async and await to load bid data.
I do one sample that using ListView to display 100000 records.
<StackLayout>
<Label Text="test ui in xamarin.forms asyn" />
<ActivityIndicator IsRunning="{Binding isBusy}" IsVisible="{Binding isBusy}" />
<ListView x:Name="listview1" ItemsSource="{Binding Items}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<Label Text="{Binding name}" />
<Label HorizontalOptions="CenterAndExpand" Text="{Binding age}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
public partial class Page19 : ContentPage
{
public Page19()
{
InitializeComponent();
this.BindingContext = new ItemsViewModel();
}
}
public class ItemsViewModel:ViewModelBase
{
private bool _isBusy;
public bool isBusy
{
get { return _isBusy; }
set
{
_isBusy = value;
RaisePropertyChanged("isBusy");
}
}
public ObservableCollection<people> Items { get; set; }
public ItemsViewModel()
{
Items = new ObservableCollection<people>();
isBusy = true;
Task.Run(async () => await LoadItems());
}
public async Task LoadItems()
{
var items = new ObservableCollection<people>(); // new collection
if (isBusy)
{
await Task.Delay(10000);
// var loadedItems = ItemsService.LoadItemsDirectory();
//foreach (var item in loadedItems)
// items.Add(item);
for (int i = 0; i < 100000; i++)
{
people p = new people();
p.name = "people " + i;
p.age = i;
items.Add(p); // items are added to the new collection
}
Items = items; // swap the collection for the new one
RaisePropertyChanged(nameof(Items)); // raise a property change in whatever way is right for your VM
isBusy = false;
}
}
}
public class people
{
public string name { get; set; }
public int age { get; set; }
}
ViewModelBase is one class that implementing INotifyPropertyChanged
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I think I got a threading problem in my UWP app.
I want to do a very simple thing:
a UI with 2 numeric fields;
if a numeric value is typed in field1, I want field2 to be set with a ratio of field1 (example: field2 = ratio * field1).
I am using x:Bind and TextChanging events. For unknown reasons, I wasn't able in the XAML to "call" the TextChanging event without having an exception at startup. Therefore, I am using the Loaded event.
Here's my model class, simply called MyModel:
public class MyModel : INotifyPropertyChanged
{
private readonly uint r1 = 3;
private uint _field1;
public uint Field1
{
get { return this._field1; }
set
{
this.Set(ref this._field1, value);
if (value == 0)
{
Field2 = 0;
}
else
{
Field2 = value * r1;
}
}
}
private uint _field2;
public uint Field2
{
get { return this._field2; }
set
{
this.Set(ref this._field2, value);
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisedPropertyChanged([CallerMemberName]string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool Set<T>(ref T storage, T value, [CallerMemberName]string propertyName = null)
{
if (Equals(storage, value))
{
return false;
}
else
{
storage = value;
this.RaisedPropertyChanged(propertyName);
return true;
}
}
}
My ViewModel:
public class MyModelViewModel : INotifyPropertyChanged
{
public MyModel MyModel { get; set; }
public MyModelViewModel()
{
// Initialisation de notre page
this.MyModel = new MyModel()
{
Field1 = 0
};
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
my code behind (I'm filtering the input to avoid a cast exception):
public sealed partial class MainPage : Page
{
public MyModelViewModel ViewModel { get; set; } = new MyModelViewModel();
public MainPage()
{
this.InitializeComponent();
}
private void InitField1(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
field1.TextChanging += field1_TextChanging;
}
private void InitField2(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
field2.TextChanging += field2_TextChanging;
}
private void field1_TextChanging(TextBox sender, TextBoxTextChangingEventArgs args)
{
var error = errorTextBlock;
error.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
Regex regex = new Regex("[^0-9]+"); // All but numeric
if (regex.IsMatch(sender.Text))
{
error.Text = "Non numeric char";
error.Visibility = Windows.UI.Xaml.Visibility.Visible;
sender.Text = this.ViewModel.MyModel.Field1.ToString();
}
else
{
this.ViewModel.MyModel.Field1 = Convert.ToUInt32(sender.Text);
}
}
private void field2_TextChanging(TextBox sender, TextBoxTextChangingEventArgs args)
{
var error = errorTextBlock;
error.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
Regex regex = new Regex("[^0-9]+");
if (regex.IsMatch(sender.Text))
{
error.Text = "Non numeric char";
error.Visibility = Windows.UI.Xaml.Visibility.Visible;
sender.Text = this.ViewModel.MyModel.Field2.ToString();
}
else
{
this.ViewModel.MyModel.Field2 = Convert.ToUInt32(sender.Text);
}
}
}
Finally, my XAML:
<TextBlock Grid.Row="0" Grid.Column="0" x:Name="errorTextBlock" Text="" Visibility="Collapsed" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Field 1" />
<TextBox Grid.Row="1" Grid.Column="1" x:Name="field1" Text="{x:Bind ViewModel.MyModel.Field1, Mode=OneWay}" Loaded="InitField1" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="Field 2" />
<TextBox Grid.Row="2" Grid.Column="1" x:Name="field2" Text="{x:Bind ViewModel.MyModel.Field2, Mode=OneWay}" Loaded="InitField2" />
At runtime, if I type a non numeric char in field1, the input is filtered, field1 returns to its previous value without the screen "blinking" (that's why I use the TextChanging event and not the TextChanged). Perfect! But if I type a numeric char, field1 is correctly updated (I can see that with breakpoint), but when field2 is set, I got a native exception when RaisedPropertyChanged is called:
I'm suspecting some kind of threading error, but I'm pretty new to this kind of development. Any idea? Thanks!
Updated to use a separate 'Model' class
Here's how you can create a text box that when a number (integer) is entered into it another text box shows the entered number multiplied by another number.
Here's the UI. Note the Mode used for each binding and the second textbox is readonly because that's just for display.
<StackPanel>
<TextBlock Text="Value 1" />
<TextBox Text="{x:Bind ViewModel.MyModel.Value1, Mode=TwoWay}" />
<TextBlock Text="Value 2" />
<TextBox Text="{x:Bind ViewModel.MyModel.Value2, Mode=OneWay}" IsReadOnly="True" />
</StackPanel>
On the page I declare my Model
public MyViewModel ViewModel { get; set; } = new MyViewModel();
My ViewModel is very simple
public class MyViewModel
{
public MyModel MyModel { get; set; } = new MyModel();
}
The Model class contains the logic
public class MyModel : INotifyPropertyChanged
{
private string _value1;
public string Value1
{
get { return _value1; }
set
{
if (_value1 != value)
{
_value1 = value;
// Cause the updated value to be displayed on the UI
OnPropertyChanged(nameof(Value1));
// Is the entered value a number (int)?
int numericValue;
if (int.TryParse(value, out numericValue))
{
// It's a number so set the other value
// multiplied by the ratio
Value2 = (numericValue * 3).ToString();
}
else
{
// A number wasn't entered so indicate this
Value2 = "NaN";
}
// Cause the updated value2 to be displayed
OnPropertyChanged(nameof(Value2));
}
}
}
// We can use the automatic property here as don't need any logic
// relating the getting or setting this property
public string Value2 { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
With the above, when a number is entered for Value1 then Value2 will show a number three times as much (because I've set the ratio of 3).
You may notice that if you try the above that the change doesn't happen immediately and Value2 is only updated when the focus leaves the Value1 text box. This is because, by default, the two-way binding is only updated when focus is lost. This can easily be changed though.
If instead of using the new x:Bind method of binding we use the traditional Binding method we can force the binding to be updated whenever we want. Say, when the text is changed.
Modify the TextBox declaration like this:
<TextBox Text="{Binding ViewModel.Value1, Mode=TwoWay}"
TextChanged="TextBox_OnTextChanged" />
Note that the binding syntax is different and we've added an event.
The handler of the event is
private void TextBox_OnTextChanged(object sender, TextChangedEventArgs e)
{
var be = (sender as TextBox).GetBindingExpression(TextBox.TextProperty);
be.UpdateSource();
}
This forces the binding to update but there's another change we must make as well.
With the x:Bind syntax it tries to bind to the page. With the older Binding syntax it binds to the DataContext of the page. To make these the same, update the page constructor like this
public MainPage()
{
this.InitializeComponent();
this.DataContext = this;
}
Now the app will work again and Value2 will be updated after every key press in the Value1 text box.
I Am working with a ListView Control in XF application. My XAML Code looks like this.
<ListView ItemsSource="{Binding RechargeList}" HasUnevenRows="True" VerticalOptions="FillAndExpand">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding Path=SelectedParkingID}" TextColor="Red" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
While my code behind looks like
private ObservableCollection<Recharge> _RechargeList = new ObservableCollection<Recharge>();
public ObservableCollection<Recharge> RechargeList
{
get
{
return _RechargeList;
}
set
{
SetProperty(ref _RechargeList, value);
}
}
And I add Items to Collection in DelegateCommand Event
RechargeList.Add(new Recharge() { SelectedParkingIDParkingID = ParkingID, RechargeAmount = double.Parse(RechargeAmount), BalanceAmount = 10 });
However, the Listview fails to refresh. Could some one help me ?
Looks like you have a typo
<TextCell Text="{Binding Path=SelectedParkingID}" TextColor="Red" />
Should be
<TextCell Text="{Binding Path=SelectedParkingIDParkingID }" TextColor="Red" />
based on what your model looks like. If you try to bind to a property that doesn't exist, it fails softly. So you're adding an item, but the TextCell doesn't render since it has no content.
Please try to implement INotifyPropertyChanged interface in your class.
public class Data : INotifyPropertyChanged
{
// boiler-plate
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetField<T>(ref T field, T value, string propertyName)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
// props
private string name;
public string Name
{
get { return name; }
set { SetField(ref name, value, "Name"); }
}
}
Each property is then just something like:
private string name;
public string Name
{
get { return name; }
set { SetField(ref name, value, "Name"); }
}
I have a listbox, with custom items. Code:
<ListBox Height="600" HorizontalAlignment="Left" Margin="7,6,0,0" Name="friendList" VerticalAlignment="Top" Width="449" ItemsSource="{Binding Friends}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<toolkit:WrapPanel />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="5,0">
<Image Height="120" HorizontalAlignment="Left" Name="image" Stretch="Fill" VerticalAlignment="Top" Width="120" Source="{Binding ImageUri}" GotFocus="image_GotFocus"/>
<CheckBox Height="78" HorizontalAlignment="Left" Margin="65,63,0,0" x:Name="selectedChckbox" VerticalAlignment="Top" Width="55" IsChecked="{Binding Selected, Mode=TwoWay}"/>
<TextBlock Height="58" HorizontalAlignment="Left" Margin="0,122,0,0" x:Name="nameTextBlck" VerticalAlignment="Top" Text ="{Binding Title}" Width="120" TextWrapping="Wrap" GotFocus="name_GotFocus"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I've created a veiwmodel for the values for binding, and when I click on one item I want to change the checkbox state like so:
friendSelectionViewModel.Friends[_selectFriendContent.friendList.SelectedIndex].Selected = !friendSelectionViewModel.Friends[_selectFriendContent.friendList.SelectedIndex].Selected;
The ViewModel Code:
public class FacebookFriendSelectionViewModel : INotifyPropertyChanged
{
public FacebookFriendSelectionViewModel()
{
Friends = new ObservableCollection<TempFriends>();
}
/// <summary>
/// A collection for MenuItemViewModel objects.
/// </summary>
public ObservableCollection<TempFriends> Friends { get; private set; }
public void AddItem(TempFriends item)
{
Friends.Add(item);
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
public class TempFriends
{
bool _selected;
public string Title { get; set; }
public string ImageUri { get; set; }
public bool Selected {
get
{
return _selected;
}
set
{
_selected = value;
OnPropertyChanged("Selected");
}
}
public string Id { get; set; }
public TempFriends(string title, string imageUir, bool selected, string id)
{
Title = title;
ImageUri = imageUir;
_selected = Selected = selected;
Id = id;
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
But the only way the listbox gets the values updated, if I set the datacontext to null and than assign the viewmodel aggain like so:
_selectFriendContent.DataContext = null;
_selectFriendContent.DataContext = friendSelectionViewModel;
But this takes about 5-10 seconds to refresh the list. I know there is a better solution, I just cant figure out how.
Thanks in advance!
TempFriends class doesn't implement INotifyPropertyChanged as far as I see. Just add public class TempFriends : INotifyPropertyChanged
I have in my Xaml a pivot control :
<controls:Pivot ItemsSource="{Binding ObjectList}">
<controls:Pivot.HeaderTemplate>
<DataTemplate>
<TextBlock />
</DataTemplate>
</controls:Pivot.HeaderTemplate>
<controls:Pivot.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Value1}" />
<TextBlock Text="{Binding Value2}" />
</StackPanel>
</DataTemplate>
</controls:Pivot.ItemTemplate>
</controls:Pivot>
My ViewModel is :
public class MyObject
{
public string Value1 { get; set; }
public string Value2 { get; set; }
}
public class MyViewModel : ViewModelBase
{
public const string ObjectListPropertyName = "ObjectList";
private ObservableCollection<MyObject> _objectList;
public ObservableCollection<MyObject> ObjectList
{
get
{
return _objectList;
}
private set
{
if (_objectList == value)
return;
_objectList = value;
RaisePropertyChanged(ObjectListPropertyName);
}
}
private DispatcherTimer timer;
public MyViewModel()
{
ObservableCollection<MyObject> collection = new ObservableCollection<MyObject>
{
new MyObject {Value1 = "One"},
new MyObject {Value1 = "Two"},
new MyObject {Value1 = "Tree"}
};
ObjectList = collection;
timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(2)};
timer.Tick += timer_Tick;
timer.Start();
}
void timer_Tick(object sender, EventArgs e)
{
foreach (MyObject myObject in _objectList)
{
myObject.Value2 = "Something";
}
Application.Current.RootVisual.Dispatcher.BeginInvoke( () => RaisePropertyChanged(ObjectListPropertyName));
}
}
When the timer_tick is reached, I supposed the pivot control to refresh with the new values ... but I can't see any changes.
What do I miss ?
Thanks in advance for your help
I'm guessing that possibly updating the members of the list without updating the list itself is the problem. When you raise the property changed event - it is for the entire collection. The collection is still pointing to an equal reference of itself, despite the fact that the members have changed.
Try placing a breakpoint in the setter and see if the property changed event is fired.