Get data binded properties in code behind - c#

In my XAML, I have this property, which works like a charm :
DataContext="{Binding Chat, Source={StaticResource Locator}}"
It is binded correctly, showing design time data etc.
However now I need to get data from textBox :
<TextBox Text="{Binding MessageInput, Mode=TwoWay}"/>
And I do not know, how to access this textBox (or binded MessageInput string) from code behind.

The ugly way:
string res = (DataContext as [TypeOfYourViewModel]).MessageInput;
A little better way:
<TextBox Name="tbMessageInput" ...>
and
string res = tbMessageInput.Text

if you need to access the textproperty then you can create a reference to the viewmodel and obtain the text from there. You can replace the chatViewModel with your viewmodel in my code
private string GetText()
{
ChatViewModel vm = this.DataContext as ChatViewModel;
if(vm != null)
return vm.MessageInput;
else
return string.Empty;
}

Related

C# WPF MVVM Textbox Clearing without breaking Command Bindings?

googling for this showed me that this is often a problem but never reallay solved.
I do have an App/Prgramm in C#, i'll try to be mvvm conform.
I do have an window, in it a UserControl show different views.
One of my view contains a textbox, the text of the textbox is bound to a proptery of the VM.
My textbox got 2 inputbindings, for "enter" and "return" - both leading to same command.
On hitting "enter" the value of the textbox should be processed, the textbox shoud be cleared and refocused ... This works .... One Time ....
Clearing the textbox with String.Empty breaks the Bindings ... this could be found in several postings here ... the most Solution is textboxname.clear() ...
But i dont have the "textboxname" in the viewmodel, only in code-behind, but all my logic is in the VM ... So can somebody pls help me sort things out, how i could clear the textbox without breaking the bindings to input text and hit enter more then one time ?
My Code
<TextBox x:Name="tb_checkinbox" Grid.Row="0" Grid.Column="1" Width="200" Height="25" VerticalAlignment="Center" HorizontalAlignment="Center" Text="{Binding CheckInNumber}">
<TextBox.InputBindings>
<KeyBinding Command="{Binding OnCheckInEnterCommand}" Key="Return"/>
<KeyBinding Command="{Binding OnCheckInEnterCommand}" Key="Enter"/>
</TextBox.InputBindings>
</TextBox>
public CheckinVM()
{
OnCheckInEnterCommand = new RelayCommand(OnCheckInEnter, CanCheckInEnter);
}
private string _checkInNumber;
public string CheckInNumber
{
get { return _checkInNumber; }
set { SetProperty(ref _checkInNumber, value); }
}
public ICommand OnCheckInEnterCommand { get; set; }
public void OnCheckInEnter(object value)
{
CheckInNumber = String.Empty;
/// More to do
}
public bool CanCheckInEnter(object value)
{
return true;
}
The assignment
CheckInNumber = string.Empty;
does not "clear" any Binding. Your conclusion is wrong.
You do however only get empty strings - after clearing - in the setter of the CheckInNumber property. In order to get property updates not only when the TextBox loses focus or you reset the source property, set UpdateSourceTrigger=PropertyChanged on the Text Binding:
<TextBox ... Text="{Binding CheckInNumber, UpdateSourceTrigger=PropertyChanged}">

Why is the DataGrid replacing a bound property with an old, invalid value when edited?

I've got a simple DataGrid bound to an ObservableCollection of view models. One column is a DataGridTemplateColumn whose CellTemplate is a TextBlock and CellEditingTemplate is a TextBox (I realize I could use a DataGridTextColumn, but I want to be explicit about which controls to use). The TextBox is configured to validate on errors.
<DataGrid
ItemsSource="{Binding People}"
AutoGenerateColumns="False"
>
<DataGrid.Columns>
<DataGridTemplateColumn Header="First Name">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding FirstName}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{Binding FirstName, UpdateSourceTrigger=PropertyChanged,
ValidatesOnDataErrors=True}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
The view model for each item (PersonViewModel) simply defines a FirstName property, and implements IDataErrorInfo for validation. I'm using the MVVM Light Toolkit for property notification.
public class PersonViewModel : ViewModelBase, IDataErrorInfo
{
private string firstName;
public string FirstName
{
get => firstName;
set => Set(nameof(FirstName), ref firstName, value);
}
public string this[string columnName]
{
get
{
if (columnName == nameof(FirstName))
{
if (string.IsNullOrEmpty(FirstName))
return "First name cannot be empty";
}
return null;
}
}
public string Error => null;
}
In addition, I've got a button that, when clicked, sets the FirstName of every person to "Hello".
public ICommand HelloCommand =>
new RelayCommand(() =>
{
foreach (var person in People)
person.FirstName = "Hello";
});
Here's the issue: when I enter an invalid FirstName (that is, I set it to the empty string), and then click on the Hello button, it correctly replaces the FirstName with "Hello". But then if I try to edit it again, the FirstName is immediately replaced with the empty string.
As a test, I made it so that it's an error to have the string "a". Doing the same steps as above, the "Hello" string is replaced with "a" in the TextBox. It's as if the TextBox doesn't know that FirstName was changed to "Hello," even though the TextBlock correctly displays it and they're both bound to the same property.
Does anyone know what's going on or ways in which to solve this issue? The behavior I expected was for the TextBox to contain the value the bound property changed to (regardless of whether there was a validation error on that TextBox).
Note: I'm using .NET 4.0 because I have to.
Obviously, the reason of this behavior is that at the time of HelloCommand executing, the TextBox that initiated validation error does not already exist. And hence clearing of validation error can't proceed usual way.
It is hard to say why the new value is not taken from the ViewModel at the moment of new TextBox creating and binding restoring. Even more interesting is where this erroneous value comes from. One may think, that if we take the new value of the FirstName property from the ViewModel and set it to TextBox.Text property in the DataContext_Changed event handler of the TextBox, then it should solve problem, because at this moment both the TextBox and the ViewModel are having a new (valid) value, and there are just no room where to take the wrong one from. But magically it still comes from somewhere :)
Happily, there is a trick that helps to get around the problem. The idea is to cancel all pending binding operations before executing HelloCommand. It is not too obvious why it should work. After all at this point the BindingExpression that caused the error is not exists. Nevertheless it works.
Give name to DataGrid:
<DataGrid x:Name="myGrid" ItemsSource="{Binding People}" AutoGenerateColumns="False">
Add click handler to the button:
<Button Content="Hello" Command="{Binding HelloCommand}" Click="Hello_Click"/>
with that code
private void Hello_Click(object sender, RoutedEventArgs e)
{
foreach (var bg in BindingOperations.GetSourceUpdatingBindingGroups(myGrid))
bg.CancelEdit();
}
UPD
As GetSourceUpdatingBindingGroups is not available in .NET 4.0, you could try this way:
private void Hello_Click(object sender, RoutedEventArgs e)
{
for (int i = 0; i < myGrid.Items.Count; i++)
{
DataGridRow row = (DataGridRow)myGrid.ItemContainerGenerator.ContainerFromIndex(i);
if (row != null && Validation.GetHasError(row))
{
row.BindingGroup?.CancelEdit();
}
}
}
It is not so elegant, but does literally the same.

Textboxes don't clear themselves when page loaded

I have list of employers that binding to data and fill from special form. When I go to form I have every text boxes clear. I fill all of them and save new employer to list. But if I try to add new employer I have textboxes with previous text in form. And variables that bind to text boxes in form are all null.
Is there way to solve problem without using solution like that: Textbox.text=null;?
I'm using MVVM pattern in my app. I'm also using catel snippets to define viewmodel and properties. There is code of ViewModel of page with employer properties:
public EmployerModifyViewModel(TransferParameter parameter, IEmployersListManage employersListManager)
{
//in "parameter" I pass values fo Current employer (it can be empty
//if we need to add new object to list or it can be some employer from list)
_employersListManager = employersListManager;
SaveEmployerCommand = new Command(OnSaveEmployerCommandExecute);
CanselSavingCommand = new Command(OnCanselSavingCommandExecute);
if (parameter.Value is EmployerClass)
{
CurrentEmployer = parameter.Value as EmployerClass;
}
}
public EmployerClass CurrentEmployer
{
get { return GetValue<EmployerClass>(CurrentEmployerProperty); }
private set { SetValue(CurrentEmployerProperty, value); }
}
/// <summary>
/// Register the CurrentEmployerBase property so it is known in the class.
/// </summary>
public static readonly PropertyData CurrentEmployerProperty = RegisterProperty("CurrentEmployer", typeof(EmployerClass), new EmployerClass());
There is example of binding to properties in xaml:
<ContentControl Content="{Binding CurrentEmployer, Mode=TwoWay}">
<ContentCntrol.Recources>
<DataTemplate DataType="{x:Type employer:EmployerClass}">
...
<TextBox Grid.Column="1"
x:Name="EmpName"
Width="300"
Height="30"
FontSize="14"
Text="{Binding Name, Mode=TwoWay}" //Property "Name" of CurrentEmployer
HorizontalAlignment="Left"
Margin="20,20,0,0"/>
I think u should use the below code where u add the new employee. Everytime the button is clicked the textboxes go empty.
txtbox_1.Text = String.Empty;
txtbox_2.Text = String.Empty;
.............
Thank you for all, problem solved. I removed ContentControl and DataTemplate from xaml and I made bindings like this "{Binding CurrentEmployer.Name, Mode=TwoWay}".

c# How to get Listbox selection from Observable Collection

I'm probably not even asking this correctly, I am new to c#, but trying to help my 14 year-old son learn. I've created a listbox with items created with an ObservableCollection. Here is the XAML:
<ListBox x:Name="listBox1" ItemsSource="{Binding}" Margin="105,205,886,63"
IsTabStop="True" SelectionChanged="PrintText"
ScrollViewer.VerticalScrollBarVisibility="Hidden" TabIndex="5" FontSize="36"
Background="Transparent" Foreground="#FF55B64C" FontFamily="Arabic Typesetting"
FontWeight="Bold" IsDoubleTapEnabled="False" SelectionMode="Single" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Name="blockNameList" Text="{Binding name}"/>
<TextBlock Text=" #"/>
<TextBlock Name="blockIdList" Text="{Binding id}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Here is how I created the ListBox Items:
var client = new HttpClient();
var uri = new Uri("http://theurlImusing");
Stream respStream2 = await client.GetStreamAsync(uri);
// DataContractJsonSerializer ser2 = new DataContractJsonSerializer(typeof(RootObject));
// RootObject feed2 = (RootObject)ser2.ReadObject(respStream2);
DataContractJsonSerializer ser = null;
ser = new DataContractJsonSerializer(typeof(ObservableCollection<RootObject>));
ObservableCollection<RootObject> feed2 = ser.ReadObject(respStream2) as ObservableCollection<RootObject>;
var cardList = new List<RootObject>();
foreach (RootObject returnfeed in feed2)
{
string cid = returnfeed.id;
string cardname = returnfeed.name;
listBox1.Items.Add(new RootObject { id=cid, name=cardname });
}
I thought I would just use the SelectionChanged="PrintText" property of the listbox so that when I clicked on a listbox item, it would just change a textblock's text value. Ultimately, that is all I am trying to do...set a textblock or textbox to be equal to the "id" value that is clicked on in the ListBox.
void PrintText(object sender, SelectionChangedEventArgs args)
{
//What do I put in here??
}
Thanks very much for any insight! I need it!!
This is something that is much easier to do using data binding. You can bind the TextBlock.Text property directly to the ListBox using an ElementName binding:
<TextBox Text="{Binding ElementName=listBox1,Path=SelectedItem.id}" />
Alternatively, if you set set SelectedValuePath="id" on the ListBox, then binding to SelectedValue will give you the "id" property:
<ListBox x:Name="listBox1" SelectedValuePath="id" ... />
<TextBox Text="{Binding ElementName=listBox1,Path=SelectedValue}" />
As a side note (as #Rachel already noted in comments): you may as well just set the ItemsSource, rather than looping through and adding each manually. All you need is this:
listBox1.ItemsSource = feed2;
Edit
Ok, if you wanted to use the procedural approach, here's how you would do it. (No one would recommend this approach, especially if you're learning/teaching. Try to make full use of data binding, and view-viewmodel separation.)
void PrintText(object sender, SelectionChangedEventArgs args)
{
var listBox = (ListBox)sender;
RootObject selectedItem = listBox.SelectedItem;
someTextBox.Text = selectedItem.id;
}
If all you want to do is click an item in the ListBox and get it to show up in the TextBox, you don't need fancy binding (in that other answer) to do it. You can simply add a MouseUp event in the ListBox XAML:
MouseUp="ListBox1_MouseUp"
This would work similar to the SelectionChanged event you wanted to use.
You then right-click that function name in the XAML page and select "Go to definition". It will create the next function for you:
private void ListBox1_MouseUp(object sender, MouseButtonEventArgs e)
{
}
Simply add in there to update the TextBox you want with the SelectedItem values from sender:
private void ListBox1_MouseUp(object sender, MouseButtonEventArgs e)
{
ListBox lstBox = (ListBox)sender;
ListBoxItem item = lstBox.SelectedItem;
if (item != null) // avoids exception when an empty line is clicked
{
someBox.Text = item.name;
someOtherBox.Text = item.id;
}
}
I later found that blockNameList and blockIdList are not accessible via intellisense because they are within the DataTemplate of the ListBox, so I put someBox and someOtherBox, as references to other TextBoxes you would have to add to the XAML, outside of the ListBox. You would not re-write data inside the ListBox on the same item by clicking it. Even if you could reach the template's TextBlock to do it, you'd just be re-writing that same item with its own values, since it would be the SelectedItem!
Even though there are those that don't recommend this approach because they like binding everything - and in some cases you want binding to occur so that controls on the page update as a result of dependencies (i.e. do one thing to cause another), I find that manual methods of clicking a button/item/control to update something are just fine and avoid all the model/MVVM BS that has taken over WPF and over-complicated it.

WPF Binding: Where a property contains the path to the value

I've got an expander with a couple of TextBlocks in the top bar which i'm using to give a title and a piece of key information.
Ideally i want to set the path to key piece of information, but i can't work out how to bind the path of the binding to another path (i apologise if i'm not making much sense!)
In the following xaml the first bit works, the second bit is what i'm struggling with.
<TextBlock Text="{Binding Path=Header.Title}"/>
<TextBlock Text="{Binding Path={Binding Path=Header.KeyValuePath}}"/>
KeyValuePath might contain something like "Vehicle.Registration" or "Supplier.Name" depending on the Model.
Can anyone point me in the right direction please? Any help gratefully received!
I don't think it can be done in pure XAML... Path is not a DependencyProperty (and anyway Binding is not a DependencyObject), so it can't be the target of a binding
You could modify the binding in code-behind instead
I haven't found a way to do this in XAML but I did this in code behind. Here is the approach I took.
Firstly, I wanted to do this for all items in an ItemsControl. So I had XAML like this:
<ListBox x:Name="_events" ItemsSource="{Binding Path=Events}">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type Events:EventViewModel}">
<TextBlock Name="ActualText" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Then, in code behind construction I subscribe to the ItemContainerGenerator:
InitializeComponent();
_events.ItemContainerGenerator.StatusChanged
+= OnItemContainerGeneratorStatusChanged;
This method looks like:
private void OnItemContainerGeneratorStatusChanged(object sender, EventArgs e)
{
if (_events.ItemContainerGenerator.Status!=GeneratorStatus.ContainersGenerated)
return;
for (int i = 0; i < _viewModel.Events.Count; i++)
{
// Get the container that wraps the item from ItemsSource
var item = (ListBoxItem)_events.ItemContainerGenerator.ContainerFromIndex(i);
// May be null if filtered
if (item == null)
continue;
// Find the target
var textBlock = item.FindByName("ActualText");
// Find the data item to which the data template was applied
var eventViewModel = (EventViewModel)textBlock.DataContext;
// This is the path I want to bind to
var path = eventViewModel.BindingPath;
// Create a binding
var binding = new Binding(path) { Source = eventViewModel };
textBlock.SetBinding(TextBlock.TextProperty, binding);
}
}
If you only have a single item to set the binding upon, then the code would be quite a bit simpler.
<TextBlock x:Name="_text" Name="ActualText" />
And in code behind:
var binding = new Binding(path) { Source = bindingSourceObject };
_text.SetBinding(TextBlock.TextProperty, binding);
Hope that helps someone.

Categories