wpf excel like grid editing? - c#

In a project I have a very tricky requirement I don't know how to solve:
I have several datagrids in a single wpf window (I use MVVM) all binded to some collection in the linked ViewModel.
The customer wants to edit each of these grids, either within the grid or in a common textbox (like in excel).
I'm banging the head on how to do the latter. What I would do is bind the textbox with a property in the viewmodel, but when the value is changed there, I need to change the value in the original property binded with the datagrid cell accordingly. In other words, I need to know what collection and which property of that collection I need to change with the data in the textbox accordingly .
I tried several ways but with no luck.
Reflection? DependencyProperty? What else?
Any help?
Thank you

Assuming that you're using the built-in WPF DataGrid, you'll need to setup your grid similarly:
<DataGrid SelectionUnit="Cell" SelectionMode="Single" ItemsSource="{Binding Data}" SelectedCellsChanged="DataGrid_OnSelectedCellsChanged">
...
</DataGrid>
Also give your TextBox a name:
<TextBox x:Name="textBox" DockPanel.Dock="Top" />
In the code-behind, you'll need to manually wire up this event, since apparently the DataGrid doesn't allow you to bind to the selected item/cell/value when using SelectionUnit="Cell":
private void DataGrid_OnSelectedCellsChanged(object sender, SelectedCellsChangedEventArgs e)
{
if (e.AddedCells.Count == 0)
this.textBox.SetBinding(TextBox.TextProperty, (string) null);
else
{
var selectedCell = e.AddedCells.First();
// Assumes your header is the same name as the field it's bound to
var binding = new Binding(selectedCell.Column.Header.ToString())
{
Mode = BindingMode.TwoWay,
Source = selectedCell.Item,
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
};
this.textBox.SetBinding(TextBox.TextProperty, binding);
}
}
I tried getting this done without code-behind but after looking around it didn't seem like this was possible.

In addition to tencntraze answer I used this code to get te property bound to the cell
var property = (selectedCell.Column.ClipboardContentBinding as Binding).Path.Path;

Related

GridViewItem IsSelected binding in UWP App

We have a requirement to display the images in a GridView incrementally. So to find the selected items in GridView , IsSelected property of the GridView item has bind with corresponding binding objects property of CLR object (property of the GridView's ItemSource type). Since, UWP does not support RelativeSouce and setter binding in style, so after doing some search on internet we found the below code.
public class GridViewEx : GridView
{
protected override void PrepareContainerForItemOverride(Windows.UI.Xaml.DependencyObject element, object item)
{
base.PrepareContainerForItemOverride(element, item);
var gridItem = element as GridViewItem;
var binding = new Binding { Mode = BindingMode.TwoWay, Source = item, Path = new PropertyPath("IsSelected") };
gridItem.SetBinding(SelectorItem.IsSelectedProperty, binding);
}
}
But it seems that there is a flaw with the above approach. Whenever the page is being scrolled down to load next set of photos then the previous selected items are losing their selection.
Is anyone has experienced this issue before or any suggestions to solve the above problem?
Move IsSelected to the model class and bind it to a user control. The user control actually is the cell and will be put in your data template.
<GridView.ItemTemplate>
<DataTemplate>
<controls:MyCustomControl IsSelected="{Binding IsSelected}"/>
</DataTemplate>
</GridView.ItemTemplate>
In MyCustomControl, you will handle difference visual state to show the selected status of item. This way your ViewModel dont have to "know" the list at all, when you need just get list of selected item from your list of models.

WPF Binding DataTable column to a textbox

I'm getting started with WPF and finding it difficult to get even the most simple binding working. Here's some givens...
I have an object that queries an existing database and returns a "datatable" object, the data comes back (and for test purposes, only a single row and single column called "MyTextColumn")
The data table is not "Strongly typed" as I've read in other places trying to force the issue of strongly typed objects. I want to understand the underlying mechanisms from the code-behind perspective, AND not from the XAML perspective. From reading, apparently you can't bind directly to a data table, but you can to the "DefaultView" of a DataTable object (makes no sense to me since they point to same record (or set of records, with exception of say a filter of some type).
So, in the XAML portion of the window,
<src:MyWindow blah, blah >
<Grid Name="grdTesting">
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Label Name="lblMyTextColumn"
Content="Just the label"
Grid.Row="0" Grid.Column="0 "/>
<TextBox Name="txtMyTextColumn"
Grid.Row="0" Grid.Column="1"
Width="120" />
</Grid>
</src:MyWindow>
So now, I'm in the code-behind, what I've read is you have to have a BindingListCollectionView oView;
public partial class MyWindow : Window
{
BindingListCollectionView oView;
MyQueryingManager oTestMgr;
public MyWindow()
{
InitializeComponent();
oTestMgr = new MyQueryingManager();
DataTable oResults = oTestMgr.GetTheData();
// Data context for the form bound to the Table retrieved
oView = new BindingListCollectionView( oResults.DefaultView );
// One place said the Window should get the binding context
DataContext = oView;
// another indicated the grid... Just need to know which I SHOULD be using
grdTesting.DataContext = oView;
// Now, for my binding preparation...
Binding bindMyColumn = new Binding();
bindMyColumn.Source = oView;
bindMyColumn.Path = new PropertyPath("MyTextColumn");
txtMyTextColumn.SetBinding( TextBox.TextProperty, bindMyColumn );
}
}
So... what am I missing here... Should be simple, nothing complex, I have a data table, with a record, that has a value. Run the form (no matter binding context to the Window or the Grid), and the record value does not show in the textbox control. Once I understand the behavior on a single textbox, I can go on with all the other elements (validation, input mask, formatting, etc), but am stuck right at the gate on this one.
Thanks
First, you can bind to a DataTable but you can also use the default view (which is a DataView) etc.
Usually, you bind a DataTable to an ItemsControl or a control that derives from it, such as ListBox, DataGrid etc. Then each container will get a DataRow (or DataRowView) and the binding will be easy.
Since you are binding it directly to a TextBox inside a Grid you would have to specify both Row and Column in the binding. The correct path to bind to the column named "MyTextColumn" in the first row is Rows[0][MyTextColumn]
Try this
Binding bindMyColumn = new Binding();
bindMyColumn.Source = oResults;
bindMyColumn.Path = new PropertyPath("Rows[0][MyTextColumn]");
txtMyTextColumn.SetBinding(TextBox.TextProperty, bindMyColumn);
A problem if you're binding directly to the DataTable is that it doesn't implement INotifyPropertyChanged so the UI won't know that the value has changed if it is changed from some other source. In this case, you can use a DataView instead. The binding syntax will be a little different here since you access the DataRowViews directly with the index operator.
dataView = oResults.DefaultView;
// Now, for my binding preparation...
Binding bindMyColumn = new Binding();
bindMyColumn.Source = dataView;
bindMyColumn.Path = new PropertyPath("[0][MyTextColumn]");
txtMyTextColumn.SetBinding(TextBox.TextProperty, bindMyColumn);
A Grid is simply a layout panel (think spreadsheet). You have to manually place controls into the cells.
What you are looking for is probably a DataGrid, which has the ability to generate columns automatically using reflection (or you can define them yourself). The WPF Toolkit from Microsoft has one you can use if you don't want to go with one of the many 3rd party data grid controls.
You are using a Grid which is a layout control, which means that it has visual elements as children, not items. You should use an ItemsControl like Listbox for example, and bind the ItemsSource property to your collection.

How to prevent datagrid from refreshing data?

Here's the scenario:
Two toolkit data grids, side-by-side
Grid A is readonly and cannot be changed
Grid B's contents can be changed and saved using a save button under it
I need Grid A to stay the same until the user clicks the save button, regardless of any changes Grid B may or may not have. When I bind to the property below both grids change when Grid B changes. I want to avoid this.
What's the best approach to do this? Both grids are currently binding to the below property:
public EntitySet<SomeEntity> SomeEntities
{
get { return _entity; }
set
{
if (_entity != value)
{
_entity= value;
OnPropertyChanged("SomePropertyChanged");
}
}
}
Set the binding for Grid A to OneTime.
i.e.
Text="{Binding Path=Age, Mode=OneTime}"
Maybe instead of completely switching out the collection of SomeEntities that the Grid is binding to, maybe use an ObservableCollection, then update on a per item basis in the ObservableCollection. Then use the Mode=OneTime that Derek mentions.
You can create two EntitySets, one for each DataGrid. After saving, you have to update set binded to read-only DataGrid.
Got it to work by using a DataGridTemplateColumn with OneTime binding. For example,
<sdk:DataGridTemplateColumn>
<sdk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Enabled, Mode=OneTime}"></TextBlock>
</DataTemplate>
</sdk:DataGridTemplateColumn.CellTemplate>
</sdk:DataGridTemplateColumn>

Updating the DisplayMember of a ListBox

This question deals with a dinky little Winforms GUI. Let it be known that I have basic knowledge of data bindings and INotifyPropertyChanged and use both in my ViewModels in WPF. But I don't know Winforms. This is for a school assignment.
So I have a class that has a DisplayName property. I also have a ListBox whose Items are a sequence of instances of my class. I have pointed myListBox.DisplayMember = "DisplayName"; After changing a value in an instance of my class that will cause the DisplayName property to return a different value, how do I tell my ListBox to pull the DisplayName property again to refresh its value?
I needed to do the same thing but with a combobox. The workaround I found is to clear and reset the DisplayMember property.
This worked:
myComboBox.DisplayMember = null;
myComboBox.DisplayMember = "DisplayName";
It's important to note that this is perhaps not the best solution as it will cause multiple SelectedValueChanged events but the end result is successful.
Doing it this way probably requires re-binding the listbox, loosing selectedIndex etc.
One workaround is to forget about the DisplayMember property and handle the Format event of the ListBox instead. Something like (from memory) :
// untested
e.Value = (e.Item as MyClass).DisplayValue;
I know this was ages ago but I had similar problem and could not find satisfying solution and finally solved with this single line at the end after updating the values:
bindingsource.EndEdit();
Items on listbox reflects any changes entered into textboxes after Update button clicked. So after lines like this:
textbox1.DataBindings["Text"].WriteValue();
textbox2.DataBindings["Text"].WriteValue();
just insert this line:
bindingsourcevariable.EndEdit();
Hope this helps others who also encounter similar problem but haven't found the right solution
Here is solution code that does everything in XAML as opposed to back end C#. This is how I do my projects utilizing MVVM (minimizing the back end code, and if possible having no back end code)
<ListBox x:Name="lstServers" HorizontalAlignment="Left" Height="285" Margin="20,37,0,0" VerticalAlignment="Top" Width="215"
ItemsSource="{Binding Settings.Servers}"
SelectedItem="{Binding Settings.ManageSelectedServer, Mode=TwoWay}"
DisplayMemberPath="UserFriendlyName"/>
This is a listbox on the Window. The keys to point out here, which can be very tricky, are the usual ItemsSource property being set to a Settings object on my view model, which has a Servers Observable collection.
Servers is a class that has a property called UserFriendlyName.
public sealed class AutoSyncServer : ObservableModel
{
public AutoSyncServer()
{
Port = "80";
UserFriendlyName = "AutoSync Server";
Server = "localhost";
}
private string _userFriendlyName;
public string UserFriendlyName
{
get { return _userFriendlyName;}
set
{
_userFriendlyName = value;
OnPropertyChanged("UserFriendlyName");
}
}
This is a partial code snippet for you of the class itself.
The SelectedItem of the ListBox is bound to an instance of the Selected object that I store in the model view called ManageSelectedServer.
The tricky part here is the DisplayMemberPath is set to "UserFriendlyName" as opposed to "{Binding UserFriendlyName}". This is key
If you use {Binding UserFriendlyName} it will display the UserFriendlyNames in the collection but will not reflect any changes to that property.
The XAML for the TextBox where the user can update the user friendly name (which should change the text in the listbox also) is:
<TextBox x:Name="txtDisplayName" HorizontalAlignment="Left" Height="23" Margin="395,40,0,0" TextWrapping="Wrap"
Text="{Binding ElementName=lstServers,Path=SelectedItem.UserFriendlyName, UpdateSourceTrigger=PropertyChanged}"
VerticalAlignment="Top" Width="240"/>
This sets the Text property of the TextBox and binds it to the ListBox element lstServers SelectedItem property UserFriendlyName. I've also included an UpdateSourceTrigger=PropertyChanged so that any changes made to the text source notify that they have been changed.
XAML is tricky!

Paste Functionality for WPF DataGrid with DataGridTemplateColumns

I recently started using the WPF Datagrid with DataGridTemplateColumns containing the WPF AutoCompleteBox, but I'm finding trouble in implementing Clipboard.Paste functionality for these DataGridTemplateColumns.
I've managed to get Clipboard.Paste working with built-in DataGridColumns via Vishal's guide here, but it doesn't work with DataGridTemplateColumns.
Delving into the OnPastingCellClipboardContent method in the DataGridColumn class, it appears that fe.GetBindingExpression(CellValueProperty) is returning null rather than the required BindingExpression.
Can anyone point me to the right direction?
public virtual void OnPastingCellClipboardContent(object item, object cellContent)
{
BindingBase binding = ClipboardContentBinding;
if (binding != null)
{
// Raise the event to give a chance for external listeners to modify the cell content
// before it gets stored into the cell
if (PastingCellClipboardContent != null)
{
DataGridCellClipboardEventArgs args = new DataGridCellClipboardEventArgs(item, this, cellContent);
PastingCellClipboardContent(this, args);
cellContent = args.Content;
}
// Event handlers can cancel Paste of a cell by setting its content to null
if (cellContent != null)
{
FrameworkElement fe = new FrameworkElement();
fe.DataContext = item;
fe.SetBinding(CellValueProperty, binding);
fe.SetValue(CellValueProperty, cellContent);
BindingExpression be = fe.GetBindingExpression(CellValueProperty);
be.UpdateSource();
}
}
Thanks!
Using ClipboardContentBinding and setting the binding's Mode to TwoWay, seems to works.
GetBindingExpression then returns something not null (the binding on ClipboardContentBinding) and the UpdateSource does not fail.
I imagine this solution is limited to the case where you have a PropertyChanged event triggered on the source, which in turn update the control in the column's DataTemplate.
This is because for DataGridTemplateColumns there isnt a binding. The binding is taken care of in your datatemplate. The cell datatemplate just gets the item (the item in the row) and binds to it. there is no way for the column to know what is in the cell.
I have worked around this by creating my own columns. I derive from DataGridTextColumn (if i am doing one that has text input) and override the GenerateElement and GenerateEditingElement.
This way i still have the binding property that can be used for pasting.
Use ClipboardContentBinding as shown:
<DataGridTemplateColumn
Header="First Name"
SortMemberPath="FirstName"
ClipboardContentBinding="{Binding FirstName}"
>
<DatGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding FirstName}" />
</DataTemplate>
</DatGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
...
</DataGridTemplateColumn>
</DataGridTemplateColumn>
Taken from here.

Categories