Actually the 2 way binding works, but there is a problem.
I have a button on the appbar. Also I have a textbox with TwoWay binding. Now, if I am typing in the textbox, and I remove focus from the textbox (close the keyboard by pressing back key), then the Property to which the textbox text is binded gets updated.
But, if I press the AppBar Button without closing the keyboard, the property does not get updated.
Is there a simple solution to this problem?
All help is greatly appreciated.
Thank You!
Edit:
I tried this.focus on the AppBar button click, but still no luck
Edit 2:
Here is my code-
<StackPanel>
<TextBlock Text="Title" FontSize="{StaticResource PhoneFontSizeMediumLarge}" Margin="15,0,0,0"/>
<TextBox Name="TitleTB" Text="{Binding Title, Mode=TwoWay}" />
<TextBlock Text="Description" FontSize="{StaticResource PhoneFontSizeMediumLarge}" Margin="15,0,0,0"/>
<TextBox Name="DescriptionTB" Text="{Binding Description, Mode=TwoWay}" AcceptsReturn="True" MaxHeight="300" VerticalScrollBarVisibility="Auto" />
</StackPanel>
.cs code-
public CreateTaskPage()
{
InitializeComponent();
M1 = new MyClass { Description = "Description", Title = "title1" };
this.DataContext = M1;
}
private void ApplicationBarIconButton_Click(object sender, EventArgs e)
{
//save - I change the text in the textbox from title1 to title123 suppose
// But it still shows title1 if I click the appbar button without closing the keyboard
this.Focus();
MessageBox.Show(M1.Title);
}
Edit 3:
MyClass code-
public class MyClass : INotifyPropertyChanged
{
private string title;
private string description;
public string Title
{
get { return title; }
set
{
title = value;
OnPropertyChanged("Title");
}
}
public string Description
{
get { return description; }
set
{
description = value;
OnPropertyChanged("Description");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
Try this:
private void ApplicationBarIconButton_Click(object sender, EventArgs e)
{
BindingExpression expression = TitleTB.GetBindingExpression(TextBox.TextProperty);
MessageBox.Show("Before UpdateSource, Test = " + M1.Title);
expression.UpdateSource();
MessageBox.Show("After UpdateSource, Test = " + M1.Title);
}
For more Refrence about binding you can go here Data binding for Windows Phone
Just to make sure, did you properly declare the property of MyClass like below?
class MyClass {
public String Description { get; set; }
public String Title { get; set; }
}
Why dont you shift the focus from textbox to some other control in the click event of the ApplicationBarIconButton.
Related
I'm trying to update a WPF UI from the ViewModel.
The View:
<UserControl.DataContext>
<local:ConcreteObserver />
</UserControl.DataContext>
<Grid>
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding Key, UpdateSourceTrigger=PropertyChanged}" />
<Button
VerticalAlignment="Top"
HorizontalAlignment="Right"
Content="Click"
Command="{Binding TestDelegateCommand}" />
</Grid>
The ViewModel:
ConcreteObserver : Observer<Mouse>, INotifyPropertyChanged
{
private string _key;
public DelegateCommand TestDelegateCommand { get; set; }
public string Key
{
get { return _key; }
set { _key = value;
OnPropertyChanged(nameof(Key));
}
}
public ConcreteObserver ()
{
TestDelegateCommand = new DelegateCommand(UpdateGui);
}
private void UpdateGui()
{
Key = "Test refresh";
}
public override void Update(TestObject subject)
{
Key = "Test Update";
if (subject is TestObject)
{
subject.MouseAction += OnMouse;
subject.Start();
}
}
private void OnMouse(object sender, RowMouseDataEventArgs e)
{
Key += "1";
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
I have an event that is triggered when I click the mouse and the event works fine.
The problem that the UI is not updated if the Key is changed within the update method and nothing is displayed on the GUI if the Key property changes.
I set a breakpoint and watched the change from the Key property and everything works fine but the GUI doesn't recognize the change.
I tested it with a button, the changes are shown when I click on the button
Can someone explain to me why.
thank you
I have a ListView that is bound on an ObservableCollection.
<ListView Grid.Column="0" Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" BorderThickness="0" Margin="5" Name="CustomerListView" ItemsSource="{Binding Customers}" SelectedItem="{Binding Path=CurrentCustomer, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ListView.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text="{Binding FirstName}"/>
<TextBlock Margin="5,0,0,0" Text="{Binding LastName}"/>
</WrapPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
In the same View i have some TextBoxes which are meant to edit the CurrentCustomer. I also have a save button. If you click this button the modifications of the CurrentCustomer should be saved. If the button "cancel" is pressed the modifications should be discarded.
<TextBox Name="CustomerSalutationTextBox" Grid.Column="1" Grid.Row="0" Height="20px" Margin="5" Text="{Binding Path=CurrentCustomer.Salutation, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
The Problem is, if i make some changes on the currentCusomer, they are taking effect immediately.
Do you have a solution?
What you need to add in your ViewModel / the class you have a binding context to is to save what was previous in the Textfield.
And when you hit abort, u just overwrite your newValue with the old one.
I'm going to setup a small example.
class ExampleViewModel : INotifyPropertyChanged {
private string _customerLastName;
private string _customerName;
private string _initialCustomerName;
private string _initialCustomerLastName;
public string CustomerName {
get { return this._customerName; }
set {
this._customerName = value;
this.OnPropertyChanged();
}
}
public string CustomerLastName {
get { return this._customerLastName; }
set {
this._customerLastName = value;
this.OnPropertyChanged();
}
}
public ExampleViewModel(string customerName, string customerLastName) {
this.CustomerName = customerName;
this.CustomerLastName = customerLastName;
this._initialCustomerName = customerName;
this._initialCustomerLastName = customerLastName;
}
//example event handler for your abort button
private void OnAbortButtonClick(object sender, EventArgs args) {
this.CustomerName = this._initialCustomerName; //set the initial name
this.CustomerLastName = this._initialCustomerLastName; //set the initial lastName
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Alternative
As you might load your data from a database/csv file/something else, you should know the original values. When pressing the cancel button, you could invoke a CancelButtonClicked event in your ViewModel and some other class which subscribed to the ViewModels event and knows the original Model could set the original values on that viewModel instance, or just exchange the ViewModel instance with the original one.
Have a look at : https://msdn.microsoft.com/en-us/library/hh848246.aspx
class ExampleViewModel : INotifyPropertyChanged {
private string _customerLastName;
private string _customerName;
public event CancelButtonClicked CancelButtonClicked;
public string CustomerName {
get { return this._customerName; }
set {
this._customerName = value;
this.OnPropertyChanged();
}
}
public string CustomerLastName {
get { return this._customerLastName; }
set {
this._customerLastName = value;
this.OnPropertyChanged();
}
}
public ExampleViewModel(string customerName, string customerLastName) {
this.CustomerName = customerName;
this.CustomerLastName = customerLastName;
}
private void OnAbortButtonClick(object sender, EventArgs args) {
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
internal delegate void CancelButtonClicked(object sender);
public class SomeOtherClass {
private ExampleViewModel _viewModel;
public SomeOtherClass() {
this._viewModel = new ExampleViewModel("foo", "bar");
this._viewModel.CancelButtonClicked += ViewModelOnCancelButtonClicked;
}
private void ViewModelOnCancelButtonClicked(object sender) {
ExampleViewModel vm = sender as ExampleViewModel;
vm.CustomerName = "foo"; //set the initial values again
vm.CustomerLastName = "bar";
}
}
Alternative2
You could also exchange the complete VM when the event of the cancel button is invoked to retreive its original state.
Alternative3
Everytime your SelectedItem changes, you could save the current state of it by creating a copy of it. When your CancelButton is pressed, you set the SelectedItem to the copy of your original viewModel.
You'd need a copy constructor or a copy method for that purpose.
I've found out another solution. In the code behind of the view i've added following:
void saveButton_Click(object sender, RoutedEventArgs e)
{
BindingExpression be = customerFirstNameTextBox.GetBindingExpression(TextBox.TextProperty);
be.UpdateSource();
}
My textbox with UpdateSourceTrigger Explicit
<TextBox Name="customerFirstNameTextBox" Grid.Column="1" Grid.Row="2" Height="20px" Margin="5" Text="{Binding Path=CurrentCustomer.FirstName, Mode=TwoWay, UpdateSourceTrigger=Explicit}" IsEnabled="{Binding Path=IsCustomerTextEnabled}"/>
And my button
<Button Name="SaveButton" Click="saveButton_Click" Margin="5" Content="Save"/>
I begin with some example code to illustrate my problem. But i'm new at this so it could be i'm missing some basic things. But the example is pretty close to what i would like to do.
XAML (main window):
<StackPanel>
<Button Click="ButtonRemove_Click">Remove</Button>
<Button Click="ButtonAdd_Click">Add</Button>
<TabControl Name="TabFilter" ItemsSource="{Binding Tabs}">
<TabControl.ContentTemplate>
<DataTemplate>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding Path=TextList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Text}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</StackPanel>
C# code:
public class TestText : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public string Text
{
get
{
return _Text;
}
set
{
_Text = value;
NotifyPropertyChanged();
}
}
private string _Text;
public TestText(string text)
{
Text = text;
}
}
public class Tabs : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public ObservableCollection<TestText> TextList { get; set; }
public Tabs(ObservableCollection<TestText> list)
{
this.TextList = list;
if (TextList.Count == 0)
TextList.Add(new TestText("Testing"));
}
}
public partial class MainWindow : Window
{
public ObservableCollection<TestText> TextList { get; set; }
public ObservableCollection<Tabs> Tabs { get; set; }
public MainWindow()
{
TextList = new ObservableCollection<TestText>();
Tabs = new ObservableCollection<Tabs>();
Tabs.Add(new Tabs(TextList));
InitializeComponent();
}
private void ButtonAdd_Click(object sender, RoutedEventArgs e)
{
Tabs.Add(new Tabs(TextList));
}
private void ButtonRemove_Click(object sender, RoutedEventArgs e)
{
Tabs.RemoveAt(0);
}
}
When i start the program i get one tab containing "Testing". I then click Remove to delete the tab. When i click Add a new tab is created.
And here is my problem. Since the collection is unchanged i expect, or would like to, that the newly created tab reflects the content in the collection. It should be a tab with the content "Testing", but the tab is empty.
What am i doing wrong?
It should be a tab with the content "Testing", but the tab is empty.
What you are seeing is not that the tab is empty, but instead no tab is selected. Since you removed all existing tabs, no remaining tab can keep the selection. So when the underlying collection is changed again to add another tab, that tab does not get selected automatically. If you click on the tab though to select it, the content is still there. You can actually see this by the way the tab header looks when it’s not selected.
Further note, that all your Tabs instances share the single TestText list you created in your MainWindow. So when you change the content in any tab, you are automatically changing the content in all tabs that exist, or will exist in the future, since all instances always get the same ObservableList instance passed.
I have a window with a textbox and a submit button. When pressing the submit button, the data in the textbox should populate into the listbox and be saved.
What's the best way of doing this? I tried a recommendation (using ObservableCollection) from an earlier question I had, but I can't seem to get it work. I have tried implementing it like this:
I created a class:
public class AccountCollection
{
private string accountName;
public string AccountName
{
get { return accountName; }
set { accountName = value; }
}
public AccountCollection(string accountName)
{
AccountName = accountName;
}
}
Assigned the binding in my XAML:
<ListBox ItemsSource="{Binding AccountName, Mode=TwoWay}" IsSynchronizedWithCurrentItem="True" Height="164" HorizontalAlignment="Left" Margin="12" Name="accountListBox" VerticalAlignment="Top" Width="161" SelectionChanged="accountListBox_SelectionChanged" />
...and finally, when a user clicks the submit button from another window that contains the Submit button and textbox:
private void okBtn_Click(object sender, RoutedEventArgs e)
{
BindingExpression expression = okBtn.GetBindingExpression(accountaddTextBox.Text);
expression.UpdateSource();
}
But alas, I'm getting nowhere. I get an error message at the GetBindingExpression section:
Argument 1: cannot convert from 'string' to 'System.Windows.DependencyProperty'
What's obvious to me here is that when I created the class I didn't specify anything about the account name from the textbox, so I don't even know if the class is correct.
I'm basically confused and don't know what to do. Any help would be appreciated...
MODEL
// the model is the basic design of an object containing properties
// and methods of that object. This is an account object.
public class Account : INotifyPropertyChanged
{
private string m_AccountName;
public event PropertyChangedEventHandler PropertyChanged;
public string AccountName
{
get { return m_AccountName;}
set
{
m_AccountName = value;
OnPropertyChanged("AccountName");
}
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
ListBox XAML
<ListBox Name="MyAccounts" DisplayMemberPath="AccountName" />
CODE BEHIND
// create a collection of accounts, then whenever the button is clicked,
//create a new account object and add to the collection.
public partial class Window1 : Window
{
private ObservableCollection<Account> AccountList = new ObservableCollection<Account>();
public Window1()
{
InitializeComponent();
AccountList.Add(new Account{ AccountName = "My Account" });
this.MyAccounts.ItemsSource = AccountList;
}
private void okBtn_Click(object sender, RoutedEventArgs e)
{
AccountList.Add(new Account{ AccountName = accountaddTextBox.Text});
}
}
edit: added displaymemberpath on listbox xaml
Here is a Demo using MVVM approach
ViewModel
public class AccountListViewModel : INotifyPropertyChanged
{
ICommand AddAccountCommand {get; set;}
public AccountListViewModel()
{
AccountList = new ObservableCollection<string>();
AddAccountCommand= new RelayCommand(AddAccount);
//Fill account List saved data
FillAccountList();
}
public AddAccount(object obj)
{
AccountList.Add(AccountName);
//Call you Model function To Save you lIst to DB or XML or Where you Like
SaveAccountList()
}
public ObservableCollection<string> AccountList
{
get {return accountList} ;
set
{
accountList= value
OnPropertyChanged("AccountList");
}
}
public string AccountName
{
get {return accountName } ;
set
{
accountName = value
OnPropertyChanged("AccountName");
}
}
}
Xaml Binding
<ListBox ItemsSource="{Binding Path=AccountList}" Height="164" HorizontalAlignment="Left" Margin="12" Name="accountListBox" VerticalAlignment="Top" Width="161" />
<TextBox Text={Binding Path=AccountName}></TextBox>
<Button Command={Binding Path=AddAccountCommand}><Button>
Xaml.cs Code
# region Constructor
/// <summary>
/// Default Constructor
/// </summary>
public MainView()
{
InitializeComponent();
this.DataContext = new AccountListViewModel();
}
# endregion
The Implementation of INotifyPropertyChanged and forming porpeties is left upto you
Your ItemsSource for your ListBox is AccountName, which is only a string but not a collection.
You need to create a viewmodel (your datacontext for the view) like this:
public class ViewModel
{
public ViewModel()
{
Accounts = new ObservableCollection<string>();
}
public ObservableCollection<string> Accounts { get; set; }
}
Bind ItemsSource to Accounts property:
<ListBox ItemsSource="{Binding Accounts}" Height="164" HorizontalAlignment="Left" Margin="12" Name="accountListBox" VerticalAlignment="Top" Width="161" />
And then, in your click event handler of the button you can simple add the current value of the textbox to your collection:
private void okBtn_Click(object sender, RoutedEventArgs e)
{
Accounts.Add(accountaddTextBox.Text);
}
But don't forget to set the DataContext of your window to the class ViewModel.
In the example below:
I start program, type text, click button, see text above. Press ENTER see text again.
BUT:
I start program, type text, press ENTER, see no text.
It seems that the KeyDown event doesn't get access to the current value of the bound variable, as if it is always "one behind".
What do I have to change so that when I press ENTER I have access to the value that is in the textbox so I can add it to the chat window?
alt text http://www.deviantsart.com/upload/1l20kdl.png
XAML:
<Window x:Class="TestScroll.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="290" Width="300" Background="#eee">
<StackPanel Margin="10">
<ScrollViewer Height="200" Width="260" Margin="0 0 0 10"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto">
<TextBlock Text="{Binding TextContent}"
Background="#fff"/>
</ScrollViewer>
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal">
<TextBox x:Name="TheLineTextBox"
Text="{Binding TheLine}"
Width="205"
Margin="0 0 5 0"
KeyDown="TheLineTextBox_KeyDown"/>
<Button Content="Enter" Click="Button_Click"/>
</StackPanel>
</StackPanel>
</Window>
Code-Behind:
using System;
using System.Windows;
using System.Windows.Input;
using System.ComponentModel;
namespace TestScroll
{
public partial class Window1 : Window, INotifyPropertyChanged
{
#region ViewModelProperty: TextContent
private string _textContent;
public string TextContent
{
get
{
return _textContent;
}
set
{
_textContent = value;
OnPropertyChanged("TextContent");
}
}
#endregion
#region ViewModelProperty: TheLine
private string _theLine;
public string TheLine
{
get
{
return _theLine;
}
set
{
_theLine = value;
OnPropertyChanged("TheLine");
}
}
#endregion
public Window1()
{
InitializeComponent();
DataContext = this;
TheLineTextBox.Focus();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
AddLine();
}
void AddLine()
{
TextContent += TheLine + Environment.NewLine;
}
private void TheLineTextBox_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Return)
{
AddLine();
}
}
#region INotifiedProperty Block
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
}
Your textbox->property binding is only happening after the textbox loses focus. When you type in text and press enter, you have not set the focus anywhere else on the form. You can demonstrate this by typing in your text, clicking on the scroll viewer, then clicking back on the textbox and hitting enter. You will then see your text change in the viewer.
To get around that, update your Text property of the textbox to this.
Text="{Binding TheLine, UpdateSourceTrigger=PropertyChanged}"
http://blogs.msdn.com/wpfsdk/archive/2006/10/19/wpf-basic-data-binding-faq.aspx
See about a quarter of the way down the page: How do I make my data-bound TextBox update the source value as I type?
Does it work if you change AddLine to use TheLineTextBox.Text?
void AddLine()
{
TextContent += TheLineTextBox.Text + Environment.NewLine;
}
This way is used to WPF, on the other hand when use the silverlight the property UpdateSourceTrigger has only default and explicit value.