DataBinding issue - DataTemplate/ViewModel/ICollectionView - wpf

Initially I posted this to the PRISM4 forum but got a suggestion that I should
try this forum as well:) I'm using WPF4 BTW...
I'm running PRISM4 and I've been struggling to get my data binding to work.
I'm following the MVVM pattern and have a view model which initially loads
data from a RDBMS and wraps it in an ICollectionView.This works perfectly, the data is displayed in the bound DatGrid, but I'm struggling in my eforts when trying to persist
changes made to the data which is presented in a DataGrid declared below.
The view model publishes the ICollectionView through a read/write property,
"Results", which, as you can see has a binding mode of "TwoWay". I thought
this would be enough to persist the changes made to the state of the
checkboxes but no:( I've experimented with a number of ways to accomplish
this but the state of the checkbox is not propagated back to the view model.
I've intercepted the call to the "PlotClicked" method which is an ICommand
object but the argument being passed has an unchanged "Plot" attribute!
This is especially obvious when I click one of the column headers and the
view is sorted - the checked rows are unchecked which is the default state of the checkboxes when retrieved from the db.
What am I doing wrong here?
Many thanks in advance - I'm really stuck here:(
/Peter
<DataGrid Grid.Row="0" Name="gridResults" ItemsSource="{Binding Results,Mode=TwoWay}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Plot">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Path=Plot, Mode=TwoWay}"
HorizontalAlignment="Center"
Command="{Binding Path=DataContext.PlotClicked,Mode=OneWay, RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type DataGrid}}}"
CommandParameter="{Binding SelectedItem, RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type DataGrid}}}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
...
I tried out the suggestions pointed out to me. This is what I've done:
In the view-model I changed the Result property from ICollectionView to OC
public ObservableCollection Results { get; set; }
Added the following template resources for the UserControl making up the View
Added the following code to the DataGrid in the "Columns" section
<DataGridTemplateColumn
Header="cbTest"
x:Name="cbTest"
CellTemplate="{StaticResource IsSelectedColumnTemplate}"
CellEditingTemplate="{StaticResource IsSelectedColumnTemplateEditing}"
CanUserSort="True"
Width="Auto"
/>
After having made those changes I experimented with various settings for the UpdateSourceTrigger in the IsChecked="{Binding mentioned... in (2) above with no effect. The changes I make to the checkboxes are not transferred back to the view-model's ObservableCollection.
Again, many thanks for trying to help me out here!
* UPDATE *
Now I've experienced something REALLY SPOOOOKY:( This is what I've done:
public class ResultViewResult : IReslutViewResult
{
public bool Plot { get; set; }
public Guid ResultId { get; set; }
public DateTime Generated { get; set; }
public int Duration { get; set; }
...
This didn't work in a sense that the 'Plot property' could NEVER be set to true by clicking the checkbox column in the DataGrid! Now I did the following:
public class ResultViewResult : IReslutViewResult
{
private bool _plot;
public bool Plot
{
get
{
return _plot;
}
set
{
_plot = value;
}
}
public Guid ResultId { get; set; }
public DateTime Generated { get; set; }
public Guid ResultId { get; set; }
public DateTime Generated { get; set; }
public int Duration { get; set; }
...
The result you may ask? It works and the 'Plot' is correctly set! Now, I thought, this is weird!!! So what I did was the following (simply commenting out the private var and get/set code):
public class ResultViewResult : IReslutViewResult
{
public bool Plot { get; set; }
//private bool _plot = false;
//public bool Plot
//{
// get
// {
// return _plot;
// }
// set
// {
// _plot = value;
// }
//}
public Guid ResultId { get; set; }
public DateTime Generated { get; set; }
public int Duration { get; set; }
...
Ok, what about the result? IT WORKS!!!??? I'm stunned... I mean what's the difference between the first and the last???? I feel VERY awkward about this - I mean I want to know WHAT'S going on behind the scene here... :(

Not sure about that, but I'd suggest you to try with an ObservableCollection used as ItemsSource . I had a lot of problems like that before, all of them were solved using this kind of collection (which is btw faster and less consuming than a classic collection for refreshing purposes).
Also, try to add in the IsChecked binding the following: UpdateSourceTrigger=PropertyChanged, this could do the trick, I think the only problem here is that the source isn't updated at the right time!

Not sure if this is the issue in your case, but the DataGrid uses a variation of databinding that will not commit the changes to the source until you move off the row. This is called a BindingGroup. Maybe you're not seeing the values committed because you haven't moved off the row yet?
http://blogs.msdn.com/b/vinsibal/archive/2008/08/11/wpf-3-5-sp1-feature-bindinggroups-with-item-level-validation.aspx
One other possibility is that the binding path is somehow not correct? Have you checked the output window in VS to see if it's reporting any binding path failures?

Related

binding ListBox SelectedItem in WPF

Please do not worry about the length of the question, it's a very simple case but I spent two days till now and I don't know why binding is not working. I have a window to do a quiz, so I created QuestionsListBox that hold the list of questions, QuestionTextBlock to display the selected question text and AnswersListBox to display the options for answers. if a question has a pre-selected answer, AnswersListBox must accordingly load the answers and select the selected answer.
in the window code behind I created a dependency property to load the exam questions:
public static DependencyProperty QuestionsProperty = DependencyProperty.Register("Questions", typeof(List<Models.Questions>), typeof(Question));
public List<Models.Question> Questions
{
get { return (List<Models.Question>)GetValue(QuestionsProperty); }
set { SetValue(QuestionsProperty, value); }
}
Models are:
public class Question
{
public short QuestionNo { get; set; }
public byte[] QuestionImage { get; set; }
public string QuestionText { get; set; }
public Answer SelectedAnswer { get; set; }
public List<Answer> Answers { get; set; }
}
public class Answer
{
public short AnswerID { get; set; }
public short AnswerPower { get; set; }
public string AnswerText { get; set; }
}
in the xaml, I have the configuration as below:
<ListView x:Name="QuestionList"
ItemsSource="{Binding ElementName=Questionctl, Path=Questions}">
<ListView.ItemTemplate>
<DataTemplate DataType="{x:Type model:Question}">
<Grid>
<TextBlock Text="{Binding Question.QuestionNo}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<TextBlock x:Name="lblText"
Text="{Binding ElementName=QuestionList, Path=SelectedItem.QuestionText}">
</TextBlock>
<ListBox x:Name="AnswersListBox"
ItemsSource="{Binding ElementName=QuestionList, Path=SelectedItem.Answers}"
SelectedItem="{Binding ElementName=QuestionList, Path=SelectedItem.SelectedAnswer, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type model:Answer}">
<Grid>
<TextBlock Text="{Binding AnswerText}">
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
when I load the questions, and select any question, everything load properly. the text of the question, the list of answers, except if question has a pre-selected answer, that answer is not selected in the answer list. but if I select an answer, the model is immediately updated. if the list of answers is updating based on the binding, so why not for selected Answers?
Did I do anything wrong that prevent the selection of the answer?
I figured out the reason, so in case someone faces the same issue, he can save time in debugging and trying...
the short answer is: Problem is in filling the data source and not in binding.
I'm filling the Exam data source using a web service in JSON format, and in that case, even the selected answer is having the same values as one of the answers list, when it reach the client, selected answer will NOT be considered as one of the answers. instead, it will be considered as new Answer class having the same data.
in order to rectify the issue, I did not touch my xaml because it has nothing wrong. Instead, I loop through the list after it has been filled from webservice to re-set the selected answer as one of the available answers:
private List<Models.Question> RectifyQuestions(List<Models.Question> exam)
{
foreach(var item in exam)
{
if (item.SelectedAnswer != null)
{
item.SelectedAnswer = item.Answers.FirstOrDefault(a => a.AnswerID == item.SelectedAnswer.AnswerID);
}
}
return exam;
}

WPF: SelectedItem Binding Not Showing With DisplayMemberPath

I've got a ComboBox (inside a ListView) that used to be tied to a collection of strings. However, I'm switching to having it use a custom class instead.
Now the ComboBox is bound to an ObservableCollection of type Area. For display, I'm showing the Name property with DisplayMemberPath. This works great, but the box is no longer loading with the current value selected.
<ListView x:Name="TestListView" ItemsSource="{Binding TestViewList, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ListView.View>
<GridView>
<GridViewColumn Header="Area">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding DataContext.AreaList, ElementName=BVTWindow}" DisplayMemberPath="Name" SelectedItem="{Binding Path=Area, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
public ObservableCollection<ViewTest> TestViewList { get; private set; }
public ObservableCollection<Area> AreaList { get; private set; }
public class ViewTest : BindableBase
{
public string Description { get; set; }
public Area Area { get; set; }
}
public partial class Area
{
public long ID { get; set; }
public string Name { get; set; }
}
"Area" is the same class as the Collection, and is a property of the class the ListView's ItemsSource is bound to. Am I doing this the right way? Any help is much appreciated.
UPDATE:
I had a theory that perhaps using the "Name" property (which is a string) for display in the box made the SelectedItem attribute look for a string rather than something of type Area. I changed the class for TestViewList to use a string to keep track of Area, but that didn't change the program's behavior at all.
UPDATE 2:
Above, I've added the pertinent lines from the view model related to the ComboBox and ListView in question.
Update 3:
I've changed my Area property from inline to expanded, including the SetProperty that handles raising. It now works for new items added to the ItemSource at run-time, but is still a blank selection for on items loaded at program start.
public class ViewTest : BindableBase
{
public string Description { get; set; }
public Area Area
{
get
{
return this.area;
}
set
{
this.SetProperty(ref this.area, value);
}
}
}
Update 4:
It turns out Paul Gibson's answer was correct. The reason my existing entries weren't loading properly had to do with a logic error in the way I was loading items from the database, and not a problem with the xaml bindings. Everything works now (with respect to those ComboBoxes, at least).
Based on what you are saying I think my comment is the answer. This is the case for a single combobox. In your case you need one for every line of the grid, so you may have to programatically add the binding to members of a list by index.
In your view model if you also have (single case):
private Area _curSelArea;
public Area curSelArea
{
get { return _curSelArea; }
set
{
_curSelArea = value;
RaisePropertyChanged("curSelArea");
}
}
Then you can bind to the property with:
SelectedItem="{Binding DataContext.curSelArea . . . }"
The view model can set the initial value of curSelArea if it is known initially.
EDIT: After actually having to do this I found that someone extended the DataGridComboBoxColumn to facilitate better binding. Check out this link if you are trying to do this: http://joemorrison.org/blog/2009/02/17/excedrin-headache-35401281-using-combo-boxes-with-the-wpf-datagrid/

Binding to properties of ObservableCollection-items

interface ICar
{
UserControl SmallView{ get; }
UserControl CompleteView{ get; }
}
class ViewModel
{
ObservableCollection<ICar> Cars{ get; set;}
ObservableCollection<UserControl> SmallViews{ get; }
ObservableCollection<UserControl> CompleteViews{ get; }
}
XAML
<ItemControl ItemsSource="{Binding SmallViews}"/>
<ItemControl ItemsSource="{Binding CompleteView}"/>
I am adding ICars instances to ViewModel.Cars collection. When that happens I want the two UserControls (small and Complete) to be added in the View (XAML).
-I can get it to work as I want, by setting the ItemsSources in CodeBehind when Cars.CollectionChanged is Raised. But I fear all the collection is redrawn for all items in ItemsSource.. I only want the changes to be added, and I would like en elegant solution without a lot of CodeBehind.
This Codebehind makes it work as intended - but I would like something cleaner somthing with real Binding.
CompleteControls and SmallControls are the names for the ItemControls above, which in this solution has no binding markup :-( .
public CarsView(ViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
UpdateViews(viewModel.Cars);
viewModel.Cars.CollectionChanged += (caller, args) =>
UpdateViews(args.NewItems.Cast<ICar>());
}
private void UpdateViews(IEnumerable<ICar> newCars)
{
foreach (var car in newCars)
{
CompleteControls.Items.Add(car.CompleteView);
SmallControls.Items.Add(car.SmallView);
}
}
I see a couple of flaws in your concepts.
class ViewModel
{
ObservableCollection<ICar> Cars{ get; set;}
// This is wrong for MVVM. You don't need this.
//
// ObservableCollection<UserControl> SmallViews{ get; }
// ObservableCollection<UserControl> CompleteViews{ get; }
}
It will also get rid of this.
UpdateViews(viewModel.Cars);
viewModel.Cars.CollectionChanged += (caller, args) =>
UpdateViews(args.NewItems.Cast<ICar>());
About the ObvservableCollection you need to know 2 things.
If you create it directly from another collection it will not trigger the event.
Your args.NewItems.Cast() will always produce one item only in a collection since the ObservableCollection does not have a AddRange() method.
I see that you came from Winforms. Start by inspecting more about MVVM. it will pay off very fast. You need to remember that if you are doing anything in the code behind with controls, stop doing it, cause your doing it wrong.
You will instantiate the UserControls in XAML.
<Listbox ItemsSource={Binding Cars}>
<Listbox.ItemsTemplate>
<DataTemplate>
<StackPanel>
<my:SmallViews />
<my:CompleteViews />
</StackPanel>
</DataTemplate>
</Listbox.ItemsTemplate>
</Listbox>
You should really avoid ever dealing with any UI elements in code behind

IEditableCollectionView to filter out deleted objects

I'm using my own derived ObservableCollection that implements ICollectionViewFactory to allow me to create my own IEditableCollectionView on top of it. The (main) purpose of the view is to allow filtering out objects flagged as "deleted", so that these records aren't shown to the user but still remains within the collection, as long as they're not marked as "accepted" or rolled back.
Am I on the right track here? Or this isn't the purpose of IEditableCollectionView?
UPDATE: The collection must support adding, removing and editing records.
SECOND UPDATE: The records flagged as "deleted" must still be within the source collection, as a delete operation can be rolled back.
I think what you are after can be achieved much more easily
Say you have a Model
public class Item
{
public bool IsDeleted { get; set; }
public string Name { get; set; }
}
And your ViewModel contains a collection
public ObservableCollection<Item> MyItems { get; set; }
You can add ICollectionView property that will filter your collection by undeleted items. Here is an example:
public ICollectionView UndeletedItems { get; set; }
Filtering logic:
// Collection which will take your ObservableCollection
var itemSourceList = new CollectionViewSource { Source = MyItems };
// ICollectionView the View/UI part
UndeletedItems = itemSourceList.View;
//add the Filter
UndeletedItems.Filter = new Predicate<object>(item => !((Item)item).IsDeleted);
Then bind your View to UndeletedItems instead
<DataGrid ItemsSource="{Binding UndeletedItems}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name}"/>
</DataGrid.Columns>
</DataGrid>
This will hide deleted items while still supporting CRUD operations.
Hope this helps

TreeViewItem.ItemContainerGenerator.ContainerFromItem inconsistent results

I have a TreeView on my page. It's bound to a collection of clients containing contracts, like:
public class Client
{
public int ClientID { get; set; }
public string Name { get; set; }
public List<Contract> Contracts { get; set; }
}
public class Contract
{
public int ContractID { get; set; }
public int ClientID { get; set; }
public string Name { get; set; }
}
The XAML for my TreeView is as follows:
<sdk:TreeView x:Name="tvClientContract" ItemsSource="{Binding ClientContracts}">
<sdk:TreeView.ItemTemplate>
<sdk:HierarchicalDataTemplate ItemsSource="{Binding Path=Contracts}">
<TextBlock Text="{Binding Path=Name}" />
</sdk:HierarchicalDataTemplate>
</sdk:TreeView.ItemTemplate>
</sdk:TreeView>
Where ClientContracts is a List<Clients>. The binding works fine and I have a hierarchical grid.
The issue that I'm having is when opening the form with the TreeView on it I want to select the current Client, I currently use the following code:
TreeViewItem client = (TreeViewItem)tvClientContract.ItemContainerGenerator.ContainerFromItem(aClient);
or
TreeViewItem client = (TreeViewItem)tvClientContract.ItemContainerGenerator.ContainerFromIndex(tvClientContract.Items.IndexOf(aClient));
client.IsSelected = true;
but this returns inconsistent results, for example I open the form when client 'ABC' is selected and client will be null. I open it again when client 'ABC' is selected and it returns the correct TreeViewItem. Has anyone came across this before or know anything I can look at to help solve the issue?
I run the above code within the Loaded event of the TreeView.
I figured out what's happening here, the clue is in the MSDN documentation for the return value of ItemContainerGenerator.ContainerFromItem():
A UIElement that corresponds to the
given item. Returns null if the item
does not belong to the item
collection, or if a UIElement has not
been generated for it.
It looks like when null is returned, a UIElement hasn't been created for the item yet.
I got round this by using
tvClientContract.UpdateLayout();
to update the layout and ensure a UIElement exists before calling
ItemContainerGenerator.ContainerFromItem()
I think that there could be some condition where "UpdateLoayout will not work":
if the TreeView is in recycling mode and the item is not in the visible portion and/or also in a "add" operation where the TreeViewItem is created on another thread.
The solution is to use similar solution as I describe in:
WPF: Select TreeViewItem broken past the root level

Resources