|
Thank you! That did it!
Everything makes sense in someone's mind
|
|
|
|
|
Turns out this works only to a point....
I have a hierarcy of objects like:
Product
| Category
| Part
Until the user first clicks a Part node in the tree, the controls in the Part template do not "know" about the underlying data source. Once a Part node is clicked, then I am able to determine the data object bound to it. In other words, the XAML you provided only seems to work after a part not is initially selected.
Also related to this, the WPF treeview's SelectedItem property is read-only, so it does not show up in the XAML intellisense.. I'm not sure why/how the WPF developers ever thought this would be a good design. So, since I cannot bind to the selected item, I really have no way of determining what node was selected without using the SelectedItemChanged event (I'm using MVVM).
Bottom line, I'm still stuck with:
1. Can't tell what node is selected
2. Can't tell from the button or textbox what the node's data object is.
[UPDATE]
I just changed the XAML you gave me to
<Style TargetType="{x:Type TreeViewItem}">
<Style.Triggers>
<Trigger Property="IsMouseCaptureWithin" Value="True">
<Setter Property="IsSelected" Value="True" />
</Trigger>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="IsSelected" Value="True" />
</Trigger>
</Style.Triggers>
</Style>
I added the "IsMouseCaptureWithin" and it seems to be working. Stay tuned.
Everything makes sense in someone's mind
modified 28-Nov-11 18:03pm.
|
|
|
|
|
I'm not quite sure what you mean about having to select the part node, so you'll have to explain that further if this next part doesn't solve your issues:
There are a lot of properties that should be bindable, but are not. SelectedItem is one of them, SelectedItems on the ListView is another biggie and DialogResult is one of the biggest IMHO that should be bindable, but aren't.
You COULD use the SelectedItemChanged event in MVVM by using an Event to Command forwarder / converter, whatever the hell you want to call it. This generally looks something like:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding WindowLoadedCommand}" CommandParameter="{Binding ElementName=logonWindow}" />
</i:EventTrigger>
</i:Interaction.Triggers>
that assembly is part of the Blend SDK, but InvokeCommandAction doesn't let you forward the EventArgs as the command param, so you need a different forwarder I think you can copy EventToCommand out of MVVMLight... thats what I did in my MVVM framework.
BUT, all this is a bit messy and a PITA IMO because you need to add that XAML everywhere you want to trap events and you have to have the command mapper AND you need to define an ICommand to get the event and the handlers, etc.
So allow me to present alternative #2 ... this is what I did... for a lot of these properties (like the treeview SelectedItem and the listview SelectedItems), I added a second bindable version. VERY clean... looks just like the original property, but is now 2 way bindable. The TreeView.SelectedItem one is pretty simple:
public class TreeViewEx : TreeView
{
public static readonly DependencyProperty SelectedItemExProperty = DependencyProperty.Register("SelectedItemEx", typeof(object),
typeof(TreeViewEx), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnSelectedItemExChanged)));
public TreeViewEx()
: base()
{
SelectedItemChanged += new RoutedPropertyChangedEventHandler<object>(TreeViewEx_SelectedItemChanged);
}
public object SelectedItemEx
{
get
{
return (object)GetValue(SelectedItemExProperty);
}
set
{
SetValue(SelectedItemExProperty, value);
}
}
private static void OnSelectedItemExChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TreeViewEx treeView = d as TreeViewEx;
if ((object)treeView != null)
treeView.SelectItem(e.NewValue);
}
private void TreeViewEx_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
SetValue(SelectedItemExProperty, e.NewValue);
}
}
Basically, all I do is add a SelectedItemEx DP. If the selection changes, it just updates the value of that DP to keep it in sync. If the DP changes, it updates the selected item in the treeview to go the other way.
then just switch your view to use TreeViewEx instead of TreeView and you can bind SelectedItemsEx to your VM.
-- modified 28-Nov-11 18:39pm.
|
|
|
|
|
Ok, I think I get it.
I'm getting a comp error on this line:
treeView.SelectItem(e.NewValue);
SelectItem is not found in TreeViewEx. Looks like this should be defined in this class, but it's not there. What am I missing?
Everything makes sense in someone's mind
|
|
|
|
|
Ah, yeah... I added that method to simplify node selection and to automatically expand and select hidden nodes -- something which is kinda tricky .
public bool SelectItem(object item)
{
if ((SelectedItem != null) && (SelectedItem.Equals(item)))
return true;
if (!ExpandAndSelectItem(this, item))
{
UpdateLayout();
return ExpandAndSelectItem(this, item);
}
return true;
}
and you'll need this one too (NOTE: I was too lazy to do it, but you need to change the TreeViewItemExs to standard TreeViewItems in this function):
private bool ExpandAndSelectItem(ItemsControl parentContainer, object itemToSelect)
{
TreeViewItemEx tvi = parentContainer.ItemContainerGenerator.ContainerFromItem(itemToSelect) as TreeViewItemEx;
if ((object)tvi != null)
{
tvi.IsSelected = true;
tvi.BringIntoView();
tvi.Focus();
return true;
}
for (int nIndex = 0; nIndex < parentContainer.Items.Count; nIndex++)
{
TreeViewItemEx currentContainer = parentContainer.ItemContainerGenerator.ContainerFromIndex(nIndex) as TreeViewItemEx;
if (((object)currentContainer != null) && (currentContainer.Items.Count > 0))
{
bool bIsExpanded = currentContainer.IsExpanded;
if (currentContainer.ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated)
{
EventHandler handler = null;
handler = new EventHandler(delegate
{
if (currentContainer.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
{
if (ExpandAndSelectItem(currentContainer, itemToSelect) == false)
currentContainer.IsExpanded = false;
currentContainer.ItemContainerGenerator.StatusChanged -= handler;
}
});
currentContainer.ItemContainerGenerator.StatusChanged += handler;
if (currentContainer.IsExpanded == false)
currentContainer.IsExpanded = true;
}
else
{
if (ExpandAndSelectItem(currentContainer, itemToSelect) == false)
currentContainer.IsExpanded = bIsExpanded;
else
return true;
}
}
}
return false;
}
I think that should be all you need . Now when you change the SelectedItemEx through binding and the node is hidden, it'll auto-expand all the necessary parent nodes and bring the requested node into view.
|
|
|
|
|
OK, another problem that seems unsolvable - until some WPF Guru comes and hurls the painfully obvious answer to my face!
I have two DateTime fields in my model. They are mapped in the viewmodel as strings (look here http://joshsmithonwpf.wordpress.com/2008/11/14/using-a-viewmodel-to-provide-meaningful-validation-error-messages/[^] why).
Both DateTime fields and mapped fields are validated through IDataErrorInfo and everything works a breeze,until I want to validate the second field when the first field changes - say, I want the second date to be after the first date!I tried raising the PropertyChanged event in the setter of the first field but no luck!
Any ideas out there? Please???
|
|
|
|
|
Just because it's not the field named in the property field as part of the validation, there's no reason you can't validate the other property as part of the validation based on the other property.
|
|
|
|
|
Time for some code:
Property A:
public DateTime A
{
get
{
return _A;
}
set
{
_A=value;
RaisePropertyChanged("A");
}
}
Similarly:
Property B:
public DateTime B
{
get
{
return _B;
}
set
{
_B=value;
RaisePropertyChanged("B");
}
}
The A is validated by:
protected override String ValidateProperty(String propertyName)
{
switch (propertyName)
{
case "A":
break;
}
}
If I understand you correctly, you propose to call ValidateProperty(B) in the case "A"?
|
|
|
|
|
That is correct. If you have 2 properties dependant on each other, you need to validate both if one changes. To quote your example:-
public DateTime A
{
get
{
return _A;
}
set
{
_A = value;
RaisePropertyChanged("A");
RaisePropertyChanged("B");
}
}
public DateTime B
{
get
{
return _B;
}
set
{
_B = value;
RaisePropertyChanged("B");
RaisePropertyChanged("A");
}
}
Take a look at my article Validating User Input - WPF MVVM[^] that shows validating dependant properties.
When I was a coder, we worked on algorithms. Today, we memorize APIs for countless libraries — those libraries have the algorithms - Eric Allman
|
|
|
|
|
I'm giving it a go right now...
|
|
|
|
|
Yup - as B is a dependency of A, it makes sense to validate it alongside as well.
|
|
|
|
|
Well, it works, but sometimes it doesn't. For instance, when I select the date on A with Ctrl+Arrow, and then type over it, sometimes the B field gets validated, sometimes it doesn't. But,hey, you can't have it all, right???
P.S: By the by, don't you just hate that?
public String Property
{
get
{
return _Property;
}
get
{
_Property=value;
RaisePropertyChanged("Property");
}
}
I wish there was some kind of "one-liner"! Something like:
ObservableProperty String MyProperty;
|
|
|
|
|
I created ths data template:
<DataTemplate DataType="{x:Type models:PartModel}">
<StackPanel Orientation="Vertical"
Margin="0,1,0,1">
<StackPanel Orientation="Horizontal">
<Image Source="/Abtech.Spares.UI;component/Media/Graphics/motherboard_enabled_96x96.png"
Height="16"
Width="16"
Margin="0,0,3,0"/>
<TextBlock Text="{Binding PartNumber}"></TextBlock>
<TextBlock Text="-"
Margin="3,0,3,0"/>
<TextBlock Text="{Binding PartDescription}"></TextBlock>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label x:Name="lblTribal"
Content="Tribal"/>
<TextBox x:Name="txtTribal"
Text="{Binding Tribal}"
Width="100"/>
<Button x:Name="cmdTribal"
Command="{Binding SelectTribalCommand, Mode=TwoWay}"
Margin="2,0,0,0">
<StackPanel Orientation="Horizontal">
<Image Source="/Abtech.Spares.UI;component/Media/Graphics/Toolbar/search_16x16.png"
Margin="0,0,3,0"/>
</StackPanel>
</Button>
</StackPanel>
</StackPanel>
</DataTemplate>
At the bottom, how do I point the SelectTribalCommand to the data context, not the PartModel?
Everything makes sense in someone's mind
|
|
|
|
|
If you defined the DataTemplate in the resources section, I don't believe you can since there is no data context there. If you defined it in the regular section underneath the control, it may work as is or it may not . These things are sometimes try & pray. You often have to futz with the syntax to get it to work.
I'd try these things in this order:
1) try move the DataTemplate "inline" to the control
2) try use ElementName binding using the Window or UserControl, ie. ElementName=myWindow, Path=DataContext.SelectTribalCommand (you *may* need to use type casting here)
To do #2, you have to do #1 anyways, but I think that should work.
|
|
|
|
|
The machine I am using has :
Microsoft Silverlight
Version: 4.0.60831.0
(c) 2011 Microsoft Corporation. All rights reserved.
I have now created a silverlight project. On going to the properties of the silverlight project, it shows:
Target silverlight version : Silverlight 3
Should it not show silverlight 4? How can I get that in the drop down control please?
Thanks
|
|
|
|
|
Same reply as here[^].
The Silverlight 4 Tools for Visual Studio 2010 should add that option to Silverlight project properties.
Mark Salsbery
|
|
|
|
|
Please am trying to show the loaction of a file in my textbox but its not working. Thisi is my code is there some am during because am using WPF for the first time
Dim ofd As New Microsoft.Win32.OpenFileDialog
Dim strFileToOpen As string
ofd.FileName = ""
ofd.Title = "Choose a file to encrypt"
ofd.InitialDirectory = "C:\"
ofd.Filter = "All Files (*.*) | *.*"
If ofd.ShowDialog = Forms.DialogResult.OK Then
strFileToOpen = ofd.FileName
txtFileToOpen.Text = strFileToOpen
End If
Dammy
|
|
|
|
|
Put a breakpoint on the line strFileToOpen = ofd.FileName, and run the application in debug. Do whatever you need to do to get to the file dialog. When you hit the breakpoint (after clicking OK), take a look and see what's in ofd.FileName then step over the line. Check the value in strFileToOpen and then step over the txtFileToOpen.Text line. Again, see what's in that property.
|
|
|
|
|
in the property of the ofd.FileName shows the file and its location but the strFileToOpen is empty and in the propert of the ofd its showing that "No children available" in the CustomPlaces property
Dammy
modified 23-Nov-11 13:13pm.
|
|
|
|
|
strFileToOpen will not be set until you assign the value. Perform each step over exactly as I describe it.
|
|
|
|
|
so i did as u said and i got the error in this line
If ofd.ShowDialog = Forms.DialogResult.OK Then in the CustomPlaces the count was 0 and i could still open the dialog and the strFileToOpen property is nothing
|
|
|
|
|
OK, what error did you get? I'm not a VB.NET developer, so you're going to have to help me out here.
|
|
|
|
|
Never mind, I know what the problem is. Basically, you're attempting to assign an incompatible type back here. Dialogs return a nullable bool. What you want to do is:
Dim result? As Boolean = ofd.ShowDialog()
If result = True Then
|
|
|
|
|
thanks it worked
|
|
|
|
|
No problem, glad to be able to help.
|
|
|
|