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;
}
Related
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/
I tried to make a TextBlock in my View that will display whatever message I send to it. It just keeps appending a new line each time it writes. I want the ability to write in different font, size, and color per line.
I have found examples that do it for ListViews, and RichTextBox. I don't care what control it is. It just needs to follow the MVVM format and that is where I am having troubles with those examples.
For those that are familiar with the Command Window, how you can make a batch file and 'echo' lines to the display? That is what I am trying to do.
Found Alternate row color in Listbox and used Bind foreground of Textblock. Realized I needed to make a class to hold my string and color. Put that class in an ObservableCollection, and bind to the ObservableCollection.
My new class:
public class DisplayData
{
public string _string { get; set; }
public System.Windows.Media.Brush _color { get; set; }
public int _fontSize { get; set; }
}
XAML:
<ListBox x:Name="Progress_Window" ItemsSource="{Binding pb._displayString}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding _string}" Foreground="{Binding _color}" FontSize="{Binding _fontSize}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Where pb is my local class variable in my VM.
Code in Model:
public ObservableCollection<DisplayData> _displayString { get; set; }
...
_displayString = new ObservableCollection<DisplayData>();
string _error = "Error Opening COM Port";
_displayString.Add(new DisplayData { _string = _error, _color = System.Windows.Media.Brushes.Red, _fontSize = 20 });
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?
First, sorry for my bad english.
I have an EF entity that looks like:
class Item
{
public Guid Id { get; set; }
public string Title{ get; set; }
public Guid? ParentId { get; set; }
public ICollection<Item> Items { get; set; }
}
Now i want to load the data from that entity on a treeview... the best I could get is the follow xaml:
<TreeView Name="treeItems">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Item}" ItemsSource="{Binding Items}">
<TextBlock Text="{Binding Path=Title}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
and load the data with
var itens = from it in ctx.Item select it;
treeItems.ItemsSource = itens;
This obviously displays the data on the treeview like this:
ItemA
ItemA1
ItemA2
ItemA1 --repeated node
ItemA2 --repeated node
How can i tweak (or rewrite) my code so the treeview displays the data in hierarchical way, without the repeated nodes?
Assuming the structure of the tree is already built, you only need to include the root items in the first level of the hierarchy; so, for example, you'd write treeItems.ItemsSource = itens.Where(i => i.ParentId == null) (optionally followed by ToList()). The template is fine.
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