ListView: Edit and save SelectedItem with a Button only - c#

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"/>

Related

WPF Update UI from override method not working

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

Clearing textboxes

I'm making an input page and I'm trying to implement a reset button. After a click on the button, the UI should be empty again.
I thought that entering an empty string would deal with this. In the code it seems to work and the value does get changed to "" but in the UI the typed text stays visible (so it doesn't show the empty "" string). I also tried with string.Empty as suggested in here but that also doesn't seem to work.
Am I missing something here? I'm kinda new to programming so if I did something horribly wrong, don't laugh too hard ;)
I'm using an MVVM pattern and Fody Weaver to deal with the property changed part of the code.
The UI / XAML
<TextBlock Text="Naam:"
Grid.Column="0"
Style="{StaticResource InputInputBlock}"
/>
<TextBox Foreground="White"
Grid.Column="1"
Text="{Binding Name, Mode=TwoWay}"
Style="{StaticResource InputInputBox}"
/>
<Button Content="Reset"
Height="50"
Width="150"
Grid.Column="0"
Grid.Row="2"
VerticalAlignment="Top"
HorizontalAlignment="Center"
Style="{StaticResource FlatButton}"
Command="{Binding ResetCommand}"
/>
The view model
private string _name;
public string Name
{
get => _name;
set
{
_name = value;
}
}
public AddStakeholderViewModel()
{
ResetCommand = new RelayCommand(() => ResetForm());
}
private void ResetForm()
{
Name = " ";
}
You can implement the INotifyPropertyChanged interface in your class. This works for me:
public class Person : INotifyPropertyChanged
{
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
// Call OnPropertyChanged whenever the property is updated
OnPropertyChanged("Name");
}
}
// Declare the event
public event PropertyChangedEventHandler PropertyChanged;
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
XAML:
<TextBox Foreground="White"
Grid.Column="1"
Text="{Binding Name, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Style="{StaticResource InputInputBox}"
/>
MainWindow:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = newPerson;
}
Person newPerson = new Person();
private void button_Click(object sender, RoutedEventArgs e)
{
newPerson.Name = "";
}
}

Binding a Combobox to values of a File

I have a combo box that I type my Database Server names in it. I just want it to remeber what I have typed so far so next time it adds it to a list of items in it so I don't have to type the same Database Server name again each time I run the app.
As for saving the names I have typed in the combox, I am fine with saving them in a file, like a text file, Json, XML, whatever.
I don't know how to do the binding? and when to load the file? Can you help me with an example?
<ComboBox x:Name="serverTxt" Height="23" VerticalAlignment="Top" Text="{Binding Path=ServerNames}"/>
Here's some code that came from this answer, with a little updating and added the storage/retrieval. It should get you started at least. Note that this solution requires a second element on your window (I added a second combobox here) because it triggers on LostFocus, otherwise it will update for each character as you type.
Set up your xaml like this:
<ComboBox x:Name="comboBox" HorizontalAlignment="Left" Margin="149,43,0,0" VerticalAlignment="Top" Width="120" IsEditable="True"
ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}" Text="{Binding NewItem, UpdateSourceTrigger=LostFocus}"/>
<ComboBox x:Name="comboBox1" HorizontalAlignment="Left" Margin="349,122,0,0" VerticalAlignment="Top" Width="120"/>
Then your main window:
public partial class MainWindow : Window
{
private string _selectedItem;
private ObservableCollection<string> ServerNames;
private string fileLocation = #"C:\Temp\ServerNames.txt";
public MainWindow()
{
ServerNames = new ObservableCollection<string>();
if (File.Exists(fileLocation))
{
var list = File.ReadAllLines(fileLocation).ToList();
list.ForEach(ServerNames.Add);
}
DataContext = this;
InitializeComponent();
}
public IEnumerable Items => ServerNames;
public string SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
OnPropertyChanged("SelectedItem");
}
}
public string NewItem
{
set
{
if (SelectedItem != null)
{
return;
}
if (!string.IsNullOrEmpty(value))
{
ServerNames.Add(value);
SelectedItem = value;
}
}
}
protected void OnPropertyChanged(string propertyName)
{
var handler = this.PropertyChanged;
handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
private void Window_Closing(object sender, CancelEventArgs e)
{
if (!File.Exists(fileLocation))
{
File.Create(fileLocation);
}
File.WriteAllLines(fileLocation, ServerNames);
}
}

Bind TextBlock Value to Object

I've created my own socket class and an instance of it in MainWindow.xaml.cs, and I want to create a small little TextBlock to monitor the connection status. I've been using this specific link: WPF textblock binding in XAML
Here's the code attempt. ComUplink.cs:
public class ComUplink
{
public String ConnectionStatus = "Idle";
public Socket Socklink;
}
In MainWindow.xaml.cs:
public partial class MainWindow : Window
{
ComUpLink Uplink;
...
public void Login_Click(object Sender, RoutedEventArgs e)
{
Uplink = new ComUpLink();
}
}
AND in the XAML file:
<TextBlock x:Name="textBlock3"
TextAlignment="Right"
HorizontalAlignment="Left"
Margin="12,218,0,0"
TextWrapping="Wrap"
Text="{Binding Path=Uplink.ConnectionString}"
VerticalAlignment="Top"
Foreground="#616161"
Width="236"/>
So, my question is, why isn't this binding properly? Am I missing an implementation of INotifyPropertyChanged?
Well you made three little mistakes:
You can only bind to properties (if those values change use INotifyPropertyChanged)
You need to set the DataContext
Your Binding used the wrong property name (ConnectionString instead of ConnectionStatus)
Try those modifications:
in MainWindow.xaml.cs:
public void Login_Click(object Sender, RoutedEventArgs e)
{
this.DataContext = new ComUpLink();
}
in ComUplink.cs:
public class ComUplink : INotifyPropertyChanged
{
private String connectionStatus = "Idle";
public String ConnectionStatus
{
get
{
return this.connectionStatus;
}
set
{
this.connectionStatus = value;
this.OnPropertyChanged();
}
}
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public Socket Socklink;
}
in MainWindow.xaml:
<TextBlock x:Name="textBlock3"
TextAlignment="Right"
HorizontalAlignment="Left"
Margin="12,218,0,0"
TextWrapping="Wrap"
Text="{Binding Path=ConnectionStatus}"
VerticalAlignment="Top"
Foreground="#616161"
Width="236"/>
You first need to set the data context of the text block to be the main window or a property.
Second you need to bind to a public property not to field

C# ListBox Update Binding Text

I have a ListBox on WP8.1 and want to Bind some items in there. That works all fine, but changing a value on the ItemSource doesn't change anything in the ListBox
<ListBox x:Name="myListBox" Width="Auto" HorizontalAlignment="Stretch" Background="{x:Null}" Foreground="{x:Null}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel x:Name="PanelTap" Tapped="PanelTap_Tapped">
<Border x:Name="BorderCollapsed">
<StackPanel Margin="105,0,0,0">
<TextBlock Text="{Binding myItem.location, Mode=TwoWay}" />
</StackPanel>
</Border>
</ListBox.ItemTemplate>
</ListBox>
I bind the items via
ObservableCollection<LBItemStruct> AllMyItems = new ObservableCollection<LBItemStruct>();
with
public sealed class LBItemStruct
{
public bool ext { get; set; }
public Container myItem { get; set; }
}
public sealed class Container
{
public string location{ get; set; }
...
}
and when I now want to change the TextBlock Text, nothing happens
private void PanelTap_Tapped(object sender, TappedRoutedEventArgs e)
{
int sel = myListBox.SelectedIndex;
if (sel >= 0)
{
myListBox[sel].myItem.location = "sonst wo";
}
}
The PanelTap_Tapped gets triggered, when I tap the Panel (checked via Debug), but the TextBlock Text does not change
If you want the view to update when a property changes, then you need to have the source object implement INotifyPropertyChaned, and raise the PropertyChanged event:
public sealed class Container : INotifyPropertyChanged
{
public string location
{
get { return _location; }
set { _location = value; RaisePropertyChanged("location"); }
}
private string _location;
...
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propName)
{
var handler = PropertyChanged;
if (handler != null)
handler(new PropertyChangedEventArgs(this, propName));
}
}

Categories

Resources