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.
Related
Hi I am working on a WPF project in which I have a grid control . The itemsSource property of the grid control is bound to a datatable in my viewmodel. I am following the mvvm pattern, so my question is that I need to bind the selectedcell property of the grid control to a property in my view model class. Is it possible to determine the name of the column in which the cell resides by binding it to a property in the view model class. I know an event handler can be attached to the cell which would call a function in the code behind the view, but I dont wish to follow that approach since it would not be mvvm. Kindly help me with any suggestions.
In your XAML bind the CurrentCell property to a DataGridCellInfo in your View Model:
<DataGrid SelectionUnit="Cell"
SelectionMode="Single"
ItemsSource="{Binding MyDataTable}"
CurrentCell="{Binding SelectedCellInfo, Mode=OneWayToSource}"/>
Then in your View Model you can access the header from the bound object:
public DataGridCellInfo SelectedCellInfo
{
get { return _selectedCellInfo; }
set
{
_selectedCellInfo = value;
OnPropertyChanged("SelectedCellInfo");
_columnName = _selectedCellInfo.Column.Header;
}
}
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;
I am binding a ListBox in my MainPage to a collection of images that have been taken using the CameraCaptureTask. Everything is working correctly, although I would like to be able to change the sort order from Ascending to Descending when respective RadioButtons in my SettingsPage are checked. I have created a value in IsolatedStorage that remembers which RadioButton is checked, so that when the MainPage of my application loads, the ListBox's binding collection will be sorted and displayed accordingly. The actual sorting of my collection, however, is where I am having issues. Note, which each image in the collection, a DateTaken property is also saved.
MainPage.xaml
<ListBox x:Name="Recent" ItemsSource="{Binding Pictures}" Margin="8"
SelectionChanged="recent_SelectionChanged"
</ListBox>
Now, in my Constructor I am setting my DataContext equal to PictureRepository.Instance, which is actually populated with the images from IsolatedStorage. I am not sure where or how to change the sort order of the collection before binding. I am thinking that in fact I would probably want to bind a copy of the sorted list, and in fact not change the sort order in IsolatedStorage. I was attempting to do something of the following as referenced from Sorting Listbox Items by DateTime values
MainPage.xaml.cs
public MainPage()
{
InitializeComponent();
DataContext = PictureRepository.Instance;
//Determine which Sort Radio Button has been Checked and display collection accordingly
//Also not sure if this should be performed in the OnNavigatedTo event
if (Settings.AscendingSort.Value)
{
//PictureRepository.Instance.Pictures.OrderBy(p => p.DateTaken).First();
//DataContext = PictureRepository.Instance;
var items = Recent.Items.Cast<CapturedPicture>().OrderBy(p => p.DateTaken).ToArray();
if (Recent.Items.Count != 0)
Recent.Items.Clear();
Recent.Items.Add(items);
}
else
{
//PictureRepository.Instance.Pictures.OrderByDescending(p => p.DateTaken).First();
//DataContext = PictureRepository.Instance;
var items = Recent.Items.Cast<CapturedPicture>().OrderByDescending(p => p.DateTaken).ToArray();
Recent.Items.Clear();
Recent.Items.Add(items);
}
}
Neither option has worked, although admittedly I have never tried sorting an ObservableCollection before populating a ListBox before. Any links, assistance, or advice would be greatly appreciated in learning this concept!
I prefer to use CollectionViewSource when sorting a ListBox. Instead of changing the backend collection that you are binding to, you allow the controls to handle this.
You page xaml:
<phone:PhoneApplicationPage.Resources>
<CollectionViewSource x:Key="PicturesViewSource" Source="{Binding Pictures}">
<!-- Add for design time help. This object should return a collection of pictures
<d:Source>
<viewModels:MyFakeObject/>
</d:Source>
-->
</CollectionViewSource>
</phone:PhoneApplicationPage.Resources>
<Grid>
<ListBox ItemsSource="{Binding Source={StaticResource PicturesViewSource}}"/>
</Grid>
In your page you can modify how the ColletionViewSource is sorted by adding or removing SortDescriptions. You would do this whenever the user changes the radio button.
PicturesViewSource.SortDescriptions.Clear();
PicturesViewSource.SortDescriptions.Add(new SortDescription("DateTaken", ListSortDirection.Descending));
In WinRT App (C#) I have List<Item> items, which bind to ListBox.
Class Item has 2 fields: string Name and bool IsSelected. As you already understood, I want to bind IsSelected field to IsSelected Property of ListBoxItem.
Why I need this? Why I didn't use SelectedItems property of my ListBox?
When ListBox just loaded, I already have some Items, which must be IsSelected = true
I don't want to create another collection to store all selected items.
What I'm looking for?
I'm looking for elegant solution, like in WPF:
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
But we all know, that WinRT doesn't support bindings in setters at all.
I also check nice post in Filip Skakun blog - and this is one of solution, but I need to write some of the BindingBuilder/BindingHelper by my self.
And now, I know two way to solve my problem:
Bind SelectedItems property of ListBox and store another collection of items. - I do not like this way
Do it like Filip Skakun - if I find nothing I use this.
In ideal situation I want to use native solution for this, or maybe someone already wrote/tested nested BindingBuilder for my situation - it's will be helpful too.
How about creating a derived ListBox:
public class MyListBox : ListBox
{
protected override void PrepareContainerForItemOverride(
DependencyObject element, object item)
{
base.PrepareContainerForItemOverride(element, item);
if (item is Item)
{
var binding = new Binding
{
Source = item,
Path = new PropertyPath("IsSelected"),
Mode = BindingMode.TwoWay
};
((ListBoxItem)element).SetBinding(ListBoxItem.IsSelectedProperty, binding);
}
}
}
For some reason I have to initialize the ListBox items in behind code, the reason is too complication to tell.
The LoadFamily() is called during WPF UserControl show up.
public void LoadFamily()
{
DataTemplate listItemTemplate = this.FindResource("ManDataTemplate") as DataTemplate;
foreach (Person man in family)
{
ListBoxItem item = new ListBoxItem();
item.DataContext = man;
item.ContentTemplate = listItemTemplate;
// other initialize for item object
this.ActivityList.Items.Add(item);
}
}
In my xmal file, I define a DataTemplate.
<DataTemplate x:Key="ManDataTemplate" DataType="{x:Type local:Person}">
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
But the ListBox only contains empty text block, the person's Name doesn't not display. I don't know why, maybe the data binding is wrong, but how to do it in behind code.
Thanks for your any help! (WPF 3.5 or 4.0)
/////////////////////////////////////////////////////////////
Thanks for all your help. I found where I was wrong.
I should not add ListBoxItem into ActivityList.Items, one is UIElement, the other is >DataCollection. They are two different thing.
I should modify the code as follow:
foreach (Person man in family)
{
this.ActivityList.Items.Add(man);
ListBoxItem item = this.ActivityList.ItemContainerGenerator.ContainerFromItem(man) as ListBoxItem;
item.ContentTemplate = listItemTemplate;
// other initialize for item object
}
I don't see the benefit of creating the listboxitems manually. Just set the ItemsSource of the Listbox to the list of person (family).
There could be binding error. That's the reason why the textblock is empty. Check the Output window of VisualStudio, it will display the binding errors if exist.
HTH