Editable WPF ListBox - c#

I have a ObservableCollection that's bound to a ListBox in WPF. I want the ListBox to be editable, and for the editing changes to be saved to the collection. Since WPF doesnt provide an editable listbox, I've tried creating my own by changing the ListBox.ItemTemplate.
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Name="EditableText" Text="{TemplateBinding Content}"/>
</DataTemplate>
</ListBox.ItemTemplate>
Changing the ItemTemplate gives me editable boxes, but any changes to the textboxes dont get saved to the ObservableCollection. Is there a way to have an editable ListBox with two way binding?

You cannot do it this way.
To achieve that kind of trick, you would need your items to be "holder classes" that expose a property you can bind your textbox to.
To understand it, imagine the following pseudo sequence of calls:
class ListBox
{
Bind(Items)
{
foreach(var item in Items)
{
DataTemplate Template = LoadTemplateForItem(item.GetType()); // this is where your template get loaded
Template.Bind(item); //this is where your template gets bound
}
}
}
Your template (the DataTemplate with the listbox) is loaded and the item (which I assume is a string in your case) gets passed in.
At this point, it only knows the string, and cannot influence anything upwards. A two-way binding cannot influence the collection because the template does not know in which context it is being used, so it cannot reach back to the original collection and modify its contents.
For that matter, this is the same thing for the TextBox. If it is not given a conainer and a property name, it has nowhere to "store back" the changes.
This basically the same as passing a string into a function call. The function cannot change which string was passed in (ignoring tricks such as by-reference argument passing).
To get back to your case, you need to build a collection of objects which expose a property containing the value that needs to be edited:
public class MyDataItem
{
string Data { get; set;}
}
Then you can bind your ListBox to a collection of those items and modifiy your datatemplate:
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Name="EditableText" Text="{Binding Data, Mode=TwoWay}"/>
</DataTemplate>
</ListBox.ItemTemplate>

Bind to a model property -- i.e. a property of the data object -- rather than to a view property such as Content. For example:
// model class
public class Widget : INotifyPropertyChanged
{
public string Description { ... }
}
<!-- view -->
<DataTemplate>
<TextBox Text="{Binding Description}" />
</DataTemplate>
Note this will not work if your ItemsSource is ObservableCollection (because there's no property to bind to).

Related

An ItemTemplate Containing A Combobox - How Do I Bind To The SelectedValue?

Imagine your working on a UI for an Wedding Planner app. You'll have a list of guests and you want to display their name on the screen next to a combobox containing values of 'Will Attend' / 'Maybe' / 'No'.
I've tried to something just that....I have a collection of items inside a view model (the guests). For each of those items I want to display a Label and a ComboBox. Each ComboBox has the same values in the drop down (the possible responses).
I've created an ItemTemplate that contains a label and a combobox. I bind it to my collection of guests and it works as expected. I'm using ancestor binding so that the ComboBox's ItemsSource is bound to the list of possible responses. That works great.
What I'm struggling with is how to bind the SelectedItem to get the values the user selects? I want to have a collection of selected values on the ViewModel somehow, but I'm having a lot of trouble finding the correct words to describe this / search for it.
Can anyone help me? Am I going about this the wrong way?
You may create an enum for the attendance state and add an Attendance property to your Guest class:
public enum Attendance
{
Yes,
No,
Maybe
}
public class Guest
{
...
public Attendance Attendance { get; set; } // raise PropertyChanged event if necessary
}
Now you could set the Tag property of the ComboBox items to the appropriate enum value and bind the SelectedValue property:
<ItemsControl.ItemTemplate>
<DataTemplate>
<ComboBox SelectedValue="{Binding Attendance}" SelectedValuePath="Tag">
<TextBlock Tag="Yes">Will Attend</TextBlock>
<TextBlock Tag="No">Won't Attend</TextBlock>
<TextBlock Tag="Maybe">May Attend</TextBlock>
</ComboBox>
</DataTemplate>
</ItemsControl.ItemTemplate>

WPF Creating different ListBox row templates based on a bound value

I do not have any code at the moment to share. Just a design question.
I have a class that defines a label, and an associated entry type, which I would like to bind to a ListBox. If the type is for example, "Postal Code", I need the ListBox to create the row as a TextBlock and a TextBox. For "Yes/no", I need it to know instead to create a TextBlock with a CheckBox beside it. There will likely be 7 or 8 of these different row types.
What is the best way to approach this?
Have a look at the ItemTemplateSelector Property. This property allows you to provide custom logic for choosing which template to use for each item in a collection.
First define your various templates in a resource dictionary...
<Application>
<Application.Resources>
<DataTemplate x:Key="TextBoxTemplate">
<!-- template here -->
</DataTemplate>
<DataTemplate x:Key="CheckBoxTemplate">
<!-- template here -->
</DataTemplate>
</Application.Resources>
</Application>
Then, create a custom DataTemplateSelector...
public class MyTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var myObj= item as MyObject;
if (myObj != null)
{
if (myObj.MyType is PostalCode)
{
return Application.Resources["TextBoxTemplate"] as DataTemplate;
}
if (myObj.MyType is YesNo)
{
return Application.Resources["CheckBoxTemplate"] as DataTemplate;
}
}
return null;
}
}
Then, its just a matter of using the ItemTemplateSelector property...
<Window>
<Window.Resources>
<local:MyTemplateSelector x:Key="tempSelector" />
</Window.Resources>
<ListBox ItemSource="{Binding items}" ItemTemplateSelector="{StaticResource tempSelector}" />
</Window>
You can use the DataTrigger class.
A DataTrigger allows you to set property values when the property value of the data object matches a specified Value.
Alternatively, you can use the DataTemplateSelector class.
Typically, you create a DataTemplateSelector when you have more than one DataTemplate for the same type of objects and you want to supply your own logic to choose a DataTemplate to apply based on the properties of each data object.
The best way to approach this would be to have a collection property containing all of the items that you want to see in your ListBox, bind that collection to a control that displays lists of items, and use different data templates to change the visuals used for each type of item.
For example, you might have a postal code type:
public class PostalCodeEntry
{
public string Value { get; set; } // Implement using standard INotifyPropertyChanged pattern
}
And a "Boolean" type:
public class BooleanEntry
{
public bool Value { get; set; } // Implement using standard INotifyPropertyChanged pattern
}
You said you wanted a label for each entry type, so a base class would be a good idea:
public abstract class EntryBase
{
public string Label { get; set; } // Implement using standard INotifyPropertyChanged pattern
}
Then BooleanEntry and PostalCodeEntry would derive from EntryBase.
That's the Models sorted. You just need a collection of these so that you can bind to them from the UI. Add an appropriate collection property to your Window or ViewModel:
public ObservableCollection<EntryBase> Entries { get; private set; }
In your UI (the View, implemented in XAML), use an instance of a control that knows how to bind to a list of items and visualize them. In WPF this would be an ItemsControl or a control that derives from it such as ListBox or ListView:
<ItemsControl ItemsSource="{Binding Entries}" />
You can see how we bind the ItemsSource property to our code-behind property named Entries. The ItemsControl (and its descendants) knows how to convert those items into a visual representation. By default, your custom object (EntryBase in our example) will be converted into a string and displayed as a text block. However, by using data templates you can control how the conversion from object to visual happens.
If you add a couple of data templates to the resources section like so:
<Window ... xmlns:my="clr-namespace:---your namespace here---">
<Window.Resources>
<DataTemplate DataType="{x:Type my:PostalCodeEntry}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Label}" />
<TextBox Text="{Binding Value}" />
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type my:BooleanEntry}">
<CheckBox Content="{Binding Label}" IsChecked="{Binding Value}" />
</DataTemplate>
</Window.Resources>
Then add the <ItemsControl ... element after that, then you should see a TextBlock/TextBox combo for PostalCodeEntry types and a CheckBox for BooleanEntry types.
Hopefully if you can get this working it will give you an idea how you could extend it to cope with other model types and their matching data templates.

Read Text From TextBox in DataGrid MVVM (wpf databinding)

I have a datagrid of rows which contain data read from a web server and values I want to write into a webserver. I write the values in getting the user to input a number into the appropriate column and click an adjacent text box;
<DataGrid x:Name="datagridDERControl" HorizontalAlignment="Center" VerticalAlignment="Center" Background="#FF322D2D" Height="382" Margin="10,78,10,10" Width="972" ItemsSource="{Binding Path=NFDataSource, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<DataGrid.Columns>
<DataGridTemplateColumn Width="100" Header="Write Set Point">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Width="100" Text="{Binding Path=WriteSetPoint, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"></TextBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Width="100" Header="Global Trip">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Name="buttonGlobalTrip" Width="100" Click="buttonGlobalTrip_Click"></Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid
How do I extract the specific textbox string per row to use in my view model.
It's always difficult to answer a question where the relevant details have been omitted by the question author. However, I shall try!
You have data bound (presumably) a collection property named NFDataSource to your DataGrid.ItemsSource property. That is the collection that represents the data in your DataGrid, so to 'extract' a specific value, you need to look into your data items in your collection.
One handy property in the DataGrid class is the SelectedItem property. this enables you to data bind an object (of the same type as those in your NFDataSource collection) to this property, which accesses the data object behind the row that is currently selected in the UI:
<DataGrid ItemsSource="{Binding NFDataSource}" SelectedItem="{Binding SelectedItem}" />
Now you can utilise your SelectedItem property to access the values from the selected row in the DataGrid:
string someValue = SelectedItem.SomeProperty;
As you tagged this with MVVM and databinding, I'll assume you're using these and have just got muddled.
"I have a datagrid of rows which contain data read from a web server
and values I want to write into a webserver."
So your viewmodel has a property, which is a collection of a custom class, that represents data fetched from a webservers.
"I write the values in getting the user to input a number into the
appropriate column and click an adjacent text box"
So this VM property is two-way bound to a datagrid, so that each item in the collection represents 'one row', and the properties on those items represent your 'columns'. The user can make changes to the UI displayed values, and because of the two way databinding the VM property is also updated.
"How do I extract the specific textbox string per row to use in my
view model."
Why do you need the specific textbox string, if it is databound to a property (or rather to a property on a class contained in a collection) in your VM anyway? If you've set up your VM this way, and are using databinding, you rarely need to worry about UI specific things such as which row in a datagrid is clicked.
As Sheridan points out though, you can also bind to properties on the datagrid such as SelectedItem so that you can perform additional operations beyond just reading/writing data. SelectedItem for your datagrid will be of the type that populates your VM collection, so will have the appropriate properties.
For example, if your VM collection is an IQueryable<Person> and that is bound to the ItemsSource of the datagrid then the SelectedItem will be of type Person. You could then have a VM property called SelectedPerson which is bound to that SelectedItem, and access things like SelectedPerson.Name etc.

Best Approach for Stacking an Unknown Number of Rows in WPF

I have a WPF GUI setup like this currently:
The "Check for Third Party Updates" button will query the machine for outdated application installs and display the results, each update grouped in its own row/section with some text describing the update and a button allowing them to initiate the install.
I have a class built for third party updates that contains application name, version, installpath, message to display, etc. My question is largely how to implement the visual components. Every time the list of "apps to be updated" is iterated through and a member is found, a new row needs to be generated with common elements (button, text, picture,etc.). And I don't know how many rows might be generated, so I need to allow for the potential of scrolling down within the tab. Is a listbox control the way to go? How can I setup a visual template for the rows that are dynamically created to adhere to?
A ListBox would be a sensible approach. You would have to create a DataTemplate for the ListBoxItems and assign that to the ItemTemplate property of the ListBox, as described in Styling and Templating an ItemsControl. All the rest, like the ability to select items, or to scroll through the list, is of course done automatically by the ListBox control.
It might look like this:
<ListBox ItemsSource="{Binding ThirdPartyUpdates}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding AppIcon}" Margin="5"/>
<TextBlock Text="{Binding AppName}" Margin="5"/>
<TextBlock Text="{Binding AppVersion}" Margin="5"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The view model for the above ListBox would be something like this:
public class ThirdPartyUpdate
{
public string AppIcon { get; set; }
public string AppName { get; set; }
public string AppVersion { get; set; }
}
public class ViewModel
{
public ObservableCollection<ThirdPartyUpdate> ThirdPartyUpdates { get; set; }
}
You can use ItemsControl and bind it to a collection of your Class and use ItemsControl's template to bind your data to whichever control you want. Check out this Example
For each item in the collection you will have a row created. Surround the item control with a ScrollViewer. Set the VerticalScrollbar visibility to auto so that it will be visible only when required. And if you set a maximumheight to a value you feel right and set the height to auto. It will grow till the maximum height and the scroll bar will be visible if items are added beyond that.

TwoWay Binding Not Updating Target - Metro App

I'm building a Metro App using VS 2012 and the Windows 8 SDK. In the app, I have this class (with a corresponding struct)
// Parameter data structure for tools
public struct ToolParameter
{
public string Title { get; set; }
public Object Value { get; set; }
public string Description { get; set; }
}
// Tool that will be used to execute something on phone
public class Tool
{
public string Title{ get; set; }
public ObservableCollection<ToolParameter> Parameters { get; set; }
public string Description { get; set; }
}
On a certain page in the app, I bind an instance of the class to the dataContext of the page
this.DataContext = currentTool;
On the page, I display various information about the app, including the parameters, which I want to make editable on the page. Because of this, I'm using a TextBox to display the parameters so that it can be edited, and binding it to the "Value" member of the ToolParameter struct.
<TextBox x:Name="ParameterValue" FontSize="15" Text="{Binding Value, Mode=TwoWay}" TextWrapping="Wrap"/>
Unfortunately, when a TextBox is bound to a value, it doesn't update until it no longer has a focus, so I added a button that the user can click that will update the parameters (and change focus from the TextBox). Unfortunately, upon clicking of the button, though the focus changes, the values of the parameter in the currentTool variable is never changed. Is there something about data binding that I am missing? Might it be that the parent of the TextBox named ParameterValue (the parameters are all part of a ListView) has to be two way as well?
From what I can see, youre TextBox is binding to Value which is a property of the ToolParameter class. The DataContext for the page is of type Tool. Tool contains Parameters which is a collection of ToolParameter objects. So, the TextBox needs to be within an ItemsCollection that has the ItemsSource set to bind to the Parameters property.
Example:
<StackPanel>
<TextBlock Text="{Binding Title}"/>
<TextBlock Text="{Binding Description}"/>
<!-- showing a ListBox, but can be any ItemsControl -->
<ListBox ItemsSource="{Binding Parameters}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Value}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
Also make sure that your classes Tool and ToolParameter implement INotifyPropertyChanged and that the setter for your properties fire the PropertyChanged event
UPDATE: Adding info that was too large for a comment
This should help understand Source/Target in bindings. For your TextBox, the source of the binding is the Value property and the Target is the TextProperty of the TextBox. When the source updates, the Text will update within the TextBox. If you the TextProperty of the TextBox changes, then it will update the Value property of your object (provided mode is set to TwoWay). You're tool however will NOT update and neither will the Parameters property of the Tool class. If you wish to update the tool object when a property of a ToolParameter updates, then you will need to subscribe to the PropertyChanged event of each ToolParameter object that gets added to the Parameters collection.
Welcome to StackOverflow!
In the binding, you can specify the UpdateSourceTrigger to 'PropertyChanged':
<TextBox Text="{Binding Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
The default value, which you are experiencing, is 'LostFocus'.

Categories