|
I think it is ridiculous to have to create a workaround for a dysfunctional control. In this particular issue, the "New Row" behavior of a datagrid is so erratic that I am hesitant to use it. The editing of existing rows is easy enough, but the adding a new row is a pain in the butt that I am almost willing to mitigate the pain of by adding a small control on the screen to insert new rows into the datagrid.
Has anyone had success with new rows? My issue is primarily that the comboxbox / textbox cellediting template leaves the textbox blank when you choose an item from the combobox in the following code:
<DataGridTemplateColumn
Header="State"
Width="60">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock
Text="{Binding PropertyState.name}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox
ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}},Path=DataContext.PropertyStates, ValidatesOnDataErrors=True,UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="name"
SelectedValuePath="id"
SelectedValue="{Binding stateId}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
After I choose an item from the drop down and leave the field it is a blank textbox.
|
|
|
|
|
Ok, first of all, before I get into your question, I'm guessing you haven't worked with WPF too long or extensively? Pretty much *ALL* the WPF controls are dysfunctional and immature when compared to the WinForms and Win32 versions. You have to remember that WPF controls are built from scratch from the ground up, not built on top of other technologies. I'm working on the ListView control at the moment because I needed to implement a lot of the missing features that came for free with the WinForms/Win32/MFC versions. So far, I've written about 3000 lines of C# code and about 1000 lines of XAML. That code is *NOT* sprinkled with 10 page comments or crazy white space or anything like that. That is ~4000 lines of straight up code to implement all the missing features in the ListView control. I would only need about 25% of this code if I was using Winforms or Win32. It sucks, sure, but thats part of playing with WPF . I'm sure MSFT will bring the controls up to snuff in the next few versions of WPF as some of the controls are missing really big features.
Anyways, with that being said...
Your template does look a bit suspicious to me, unless I'm missing something:
* Seems like you normally show PropertyState.name in the TextBlock
* Seems like your ComboBox shows a list of PropertyStates, displaying the name property with a property called stateId indicating the selected item
So...
1) you might want to get rid of the UpdateSourceTrigger=PropertyChanged and add Mode=TwoWay just to make sure that is working as expected... in certain situations the default values are different then what you might expect.
2) the big problem I see here is that assuming you are writing the combobox selection to stateId correctly as two-way, how exactly is the "PropertyState" and/or "PropertyState.name" notification happening? There isn't anything in your XAML that would indicate PropertyState.name is getting a notification change. I would think the ComboBox should write the selection to PropertyState no?
|
|
|
|
|
This is my first WPF project, you are correct there. I am not one hundred percent clear on the whole dependency property situation yet, so I am not entirely sure on how to update the PropertyState.name. Should I add an OnPropertyChanged("PropertyState") to the SelectedItem? I confess I am a little murky on one way - two way updates, the documentation for that has been vexing.
I will read more into it in hopes of scraping it together.
Cheers, --EA
|
|
|
|
|
Ok, well, theres basically two main ways you can tell the WPF data binding engine that something has changed.
1) Dependency Properties (or DP for short - the whole DependencyProperty.Register... mumbo jumbo)
2) INotifyPropertyChanged (or INPC for short)
These two methods are independent of each other. A DP doesn't use INPC and vice versa. A DP has its own infrastructure to notify of updates. INPC requires you to implement the PropertyChanged event and manually send out notifications.
Convention dictates that DPs are typically only used in controls and ViewModels use INPC.
DPs typically have C# get/set wrappers. It is important that the ONLY code you have there are the GetValue() and SetValue() calls. Thats it. Not one thing more. Not a single thing . Thats because only your code calls the getter & setter. The WPF infrastructure bypasses those completely. You don't want the user call and WPF doing different things.
With IPNC, WPF will call your C# get/set methods, so its consistent there.
Heres a link on binding modes: http://msdn.microsoft.com/en-us/library/system.windows.data.bindingmode.aspx
What is "PropertyState" in your view model? Is that a DP or INPC?
I would think at the very least, you need to change to SelectedValue="{Binding PropertyState}" on the combo box. Not sure at all what "stateId" is all about.
|
|
|
|
|
PropertyState is really neither. It is actually a linked table to the item "SelectedItem" which is of type property. So the datagrid is bound to public ObservableCollection<property>AllProperties and PropertyState would be SelectedItem.PropertyState.name on a row by row basis. So it has no public characteristics of it's own, but is rather exposed through the logical element Property.
Top of the datagrid:
<DataGrid
ItemsSource="{Binding AllProperties}"
AutoGenerateColumns="False"
SelectedItem="{Binding SelectedItem}"
GridLinesVisibility="Horizontal"
CanUserDeleteRows="False"
Grid.Row="0"
RowStyle="{StaticResource RowStyle}">
ViewModel Items:
public Property SelectedItem
{
get { return _selectedItem; }
set
{
if (value == null)
return;
_selectedItem = value;
OnPropertyChanged("SelectedItem");
}
}
private void LoadProperties()
{
var query = _db.Properties.Where(p => p.claimId == _claimId && p.active);
AllProperties = new ObservableCollection<Property>(query.ToList());
}
private void LoadStates()
{
var query = _db.PropertyStates;
PropertyStates = new ObservableCollection<PropertyState>(query.ToList());
}
Does that make enough sense? I realize it is kind of a mess. SelectedItem<property> is an INPC.
|
|
|
|
|
If your combobox is showing PropertyStates, the SelectedItem is going to be a PropertyState. So you need to have an INPC of PropertyState. Both the combobox and the textblock need to bind to that property.
|
|
|
|
|
Perhaps additional code will help to clarify.
public ObservableCollection<Property> AllProperties { get; private set; }
public ObservableCollection<PropertyState> PropertyStates { get; private set; }
public ObservableCollection<StreetSuffix> StreetSuffixes { get; private set; }
public ICommand SaveCommand { get { return new RelayCommand(param => Save(), CanSaveExecute); } }
public Property SelectedItem
{
get { return _selectedItem; }
set
{
if (value == null)
return;
_selectedItem = value;
OnPropertyChanged("SelectedItem");
}
}
public StreetSuffix SelectedSuffix
{
get { return _selectedSuffix; }
set
{
if (value == null)
return;
_selectedSuffix = value;
OnPropertyChanged("SelectedSuffix");
OnPropertyChanged("SelectedItem");
}
}
#endregion
#region private helpers
private void LoadProperties()
{
var query = _db.Properties.Where(p => p.claimId == _claimId && p.active);
AllProperties = new ObservableCollection<Property>(query.ToList());
}
private void LoadStreetSuffixes()
{
var query = _db.StreetSuffixes;
StreetSuffixes = new ObservableCollection<StreetSuffix>(query.ToList());
}
private void LoadStates()
{
var query = _db.PropertyStates;
PropertyStates = new ObservableCollection<PropertyState>(query.ToList());
}
And the view:
<DataGrid
ItemsSource="{Binding AllProperties}"
AutoGenerateColumns="False"
SelectedItem="{Binding SelectedItem}"
GridLinesVisibility="Horizontal"
CanUserDeleteRows="False"
Grid.Row="0"
RowStyle="{StaticResource RowStyle}">
<DataGrid.RowValidationRules>
<local:RowDataInfoValidationRule
ValidationStep="UpdatedValue" />
</DataGrid.RowValidationRules>
<DataGrid.CellStyle>
<Style
TargetType="{x:Type DataGridCell}">
<Style.Triggers>
<Trigger
Property="IsSelected"
Value="True">
<Setter
Property="Foreground"
Value="WhiteSmoke" />
<Setter
Property="Background"
Value="LightSlateGray" />
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.CellStyle>
<DataGrid.Columns>
<DataGridTextColumn
Header="Number"
Width="54">
<DataGridTextColumn.Binding>
<Binding
Path="number">
<Binding.ValidationRules>
<local:CellDataInfoValidationRule
ValidationStep="UpdatedValue" />
</Binding.ValidationRules>
</Binding>
</DataGridTextColumn.Binding>
</DataGridTextColumn>
<DataGridTemplateColumn
Header="Direction"
Width="58">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock
Text="{Binding direction}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox
Text="{Binding direction, ValidatesOnDataErrors=True,UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn
Header="Street Name"
Width="77">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock
Text="{Binding street}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox
Text="{Binding street, ValidatesOnDataErrors=True,UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn
Header="Type"
Width="55">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock
Text="{Binding StreetSuffix.abbreviation}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox
ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}},Path=DataContext.StreetSuffixes}"
DisplayMemberPath="name"
SelectedValuePath="id"
SelectedValue="{Binding suffixId}"
SelectedItem="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}},Path=DataContext.SelectedSuffix}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn
Width="55"
Binding="{Binding unit}"
Header="Unit" />
<DataGridTemplateColumn
Header="City"
Width="75">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock
Text="{Binding city}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox>
<Binding
Path="city">
<Binding.ValidationRules>
<local:CellDataInfoValidationRule
ValidationStep="UpdatedValue" />
</Binding.ValidationRules>
</Binding>
</TextBox>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn
Header="State"
Width="60">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock
Text="{Binding PropertyState.name}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox
ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}},Path=DataContext.PropertyStates, ValidatesOnDataErrors=True,UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="name"
SelectedValuePath="id"
SelectedValue="{Binding stateId}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn
Header="Zip Code"
Width="60">
<DataGridTextColumn.Binding>
<Binding
Path="zip">
<Binding.ValidationRules>
<local:CellDataInfoValidationRule
ValidationStep="UpdatedValue" />
</Binding.ValidationRules>
</Binding>
</DataGridTextColumn.Binding>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
So "Selected Item" refers to the selected item of the datagrid, not the selected item of the combobox. Is that my issue? That I need to bind to an ancestor?
|
|
|
|
|
It looks to me like the ComboBox is using this:
ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}},Path=DataContext.PropertyStates, ValidatesOnDataErrors=True,UpdateSourceTrigger=PropertyChanged}"
So ComboBox is showing PropertyStates, that looks like ObservableCollection<PropertyState> to me, so its a collection of PropertyState objects. Thus each item in the ComboBox is a PropertyState. ComboBox.SelectedValue is going to be a PropertyState as well.
Right now you are binding ComboBox.SelectedValue to: SelectedValue="{Binding stateId}" . You aren't showing what stateId is.
The TextBlock is binding to "{Binding PropertyState.name}" which has to be a property of some sort on your ViewModel. You aren't showing that either.
So your problem is that your combobox.SelectedValue is binding to something completely different from the TextBlock, they need to bind to the same property so when you go to edit, you'll have the right item selected and when you are done editing, the TextBlock will have the correct text.
|
|
|
|
|
Binding StateId refers to the stateId of the datagrid's datacontext. Rather, it is a field on the property table, so it is one of the columns on SelectedItem<property>
So if this is all EF, as it is, then you have a Property Table and a PropertyState table with a FK - PK relationship. So if a property is in the state of CA, then the Property Table has stateId = 1, and PropertyState has Id:1|Name:California. Maybe I am too bound up in the WinForms world where the "Selected Value" of a combobox makes everything work automagically, but in this case my thought process was this:
{Binding PropertyState.name} simply gets the name of the state from the PropertyState table that belongs to whatever the stateID of the SelectedItem is. It obviously all works as it is, with the exception of a new item.
My thought on this was because the item does not yet have an item in the AllProperties collection, the UI is unable to resolve the relationship of Property (new) to PropertyState.
I could be way off base here. To maybe try a different tact, how would you deal with a datagrid in which you wanted to have a FK combo box column that constrains user choices, as in the scenario above? A simple address datagrid that has a drop down for State or Street Type or whatever?
|
|
|
|
|
I grabbed this piece of code, which I understand - Except for the "IsHitTestVisible="False" part.
What is a HitTest and what is the impact of setting/not setting this property?
<ContentControl Width="100"
Height="100"
Padding="1"
Canvas.Left="710"
Canvas.Top="300"
Style="{StaticResource DesignerItemStyle}">
<Ellipse IsHitTestVisible="False" >
<Shape.Fill>
<RadialGradientBrush Center="0.2, 0.2" GradientOrigin="0.2, 0.2" RadiusX="0.8" RadiusY="0.8">
<GradientStop Color="LightBlue" Offset="0"/>
<GradientStop Color="Blue" Offset="0.9"/>
</RadialGradientBrush>
</Shape.Fill>
</Ellipse>
</ContentControl>
Thanks
Everything makes sense in someone's mind
|
|
|
|
|
Basically, this property determines whether or not a hit test can take place against a visual element. This has the effect of selectively altering the visual tree for the purposes of hit testing. Here's an example - suppose you have a map, and you want to get the coordinates of a point on the map - but you don't want any pushpins on it to be included in the tests, then setting this to false for the pushpins means the hit test would take place against the map, and not any pushpins.
I hope that explains it.
|
|
|
|
|
Sort of.
I still don't understand what a HitTest is.
Everything makes sense in someone's mind
|
|
|
|
|
Ah. Imagine clicking on a point on the screen - the point you click at is the hit. It's a way of determining whether or not a control is affected by a click (perhaps you're writing a drag manager; and no this isn't your burly boss coming in wearing a dress)
|
|
|
|
|
Ok, I got it. So IsHitTestVisible="False" means the object does not responde to a hit test?
Everything makes sense in someone's mind
|
|
|
|
|
Yes. A reason to do this is because you might have layered your control over the top of another one, and set it so that it's invisible - it will intercept mouse clicks destined for the lower control unless you set the hit test to false.
|
|
|
|
|
ahh, ok. I get it.
Thanks
Everything makes sense in someone's mind
|
|
|
|
|
One more thing Kevin, and I apologise for not mentioning it. Setting IsHitTestVisible="false" on a container affects the children of the container as well. This is something to be very careful of because you can find that your hit testing doesn't behave quite the way you'd think it does by things higher up the tree. Here's a test for you to try:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="HitTester.MainWindow"
x:Name="Window"
Title="Hit testable window"
Width="788" Height="621">
<Canvas IsHitTestVisible="False">
<Button Content="Button" Width="75" IsHitTestVisible="True" />
</Canvas>
</Window>
|
|
|
|
|
Ok, will do. Thanks
Everything makes sense in someone's mind
|
|
|
|
|
I'm so glad POH gave you a sensible answer b/c the first thing that I thought of was if someone smacks you in the face and you react then you have been hittested. A number of other facetious replies also occurred to me but you get the point.
Never underestimate the power of human stupidity
RAH
|
|
|
|
|
Actually, my first thought was to post that it was an online exam for assassins.
|
|
|
|
|
You've been spending too much time in the Lounge listening to JSOP (and others)
Never underestimate the power of human stupidity
RAH
|
|
|
|
|
This is the most Strangest error i have ever received from Silverlight. I have each control with its own name and i just added a Link button and a Menu item , i did a copy and paste of other items and renamed them from the xaml and i added a page that will be used by that menu item and i compiled the app and when i run it i get the following Error
Line: 54
Error: Unhandled Error in Silverlight Application
Code: 2028
Category: ParserError
Message: The name already exists in the tree: EchashDev.
File:
Line: 0
Position: 0
i tried googling and a lot of people are have different scenario which might be different from mine. I am not sure why Silverlight does not point to a problem element.
Thanks
Vuyiswa Maseko,
Spoted in Daniweb-- Sorry to rant. I hate websites. They are just wierd. They don't behave like normal code.
C#/VB.NET/ASP.NET/SQL7/2000/2005/2008
http://www.vuyiswamaseko.com
vuyiswa@its.co.za
http://www.itsabacus.co.za/itsabacus/
|
|
|
|
|
I believe you've added two components with the same name (or key).
A simple search within the code may give you an idea.
Also trying cleaning your solution once.
The funniest thing about this particular signature is that by the time you realise it doesn't say anything it's too late to stop reading it.
|
|
|
|
|
thanks for your reply. It was a bit complicated than that, the problem is now resolved , here was a problem.
i Had two Projects, opened. i was doing my demo on one project , so they liked it and i copied some controls and paste from one project to another and it created a reference and now it means the control exists in two projects that are linked so it caused a Conflict.
When i was typing some namespaces , i realised that there were namespaces that i did not create and i looked at the name of the demo project and i realised that i could access its object from another class. So i went to the References in the solution explorer and there was a dead reference marked with an exclation mark and i knew it was it.
its a lesson , don't copy controls between projects unless you copy code.
I see there is a duplicate post , please delete it. i think it was created when i lost connection trying to post the question.
thanks
Vuyiswa Maseko,
Spoted in Daniweb-- Sorry to rant. I hate websites. They are just wierd. They don't behave like normal code.
C#/VB.NET/ASP.NET/SQL7/2000/2005/2008
http://www.vuyiswamaseko.com
vuyiswa@its.co.za
http://www.itsabacus.co.za/itsabacus/
modified on Friday, April 8, 2011 4:12 PM
|
|
|
|
|
We can't delete it, but you can. We'll leave it with no answers so you can remove it.
Good job on finding the solution.
|
|
|
|