Treeview events with Attached Command Behavior - wpf

I want to handle events on a treeview with ACB (http://marlongrech.wordpress.com/2008/12/04/attachedcommandbehavior-aka-acb/).
I am stuck with the bindings in the XAML file. The event is fired but I keep getting null reference exceptions in the ACB library because strategy is null:
/// <summary>
/// Executes the strategy
/// </summary>
public void Execute()
{
strategy.Execute(CommandParameter);
}
In the XAML file I added the following (excerpt):
xmlns:acb="clr-namespace:AttachedCommandBehavior;assembly=AttachedCommandBehavior"
<StackPanel x:Name="VerklaringenTreeviewPanel">
<Border x:Name="TreeviewHeaderBorder" Style="{StaticResource TreeviewBorderHeaderStyle}">
<TextBlock x:Name="tbTreeviewHeader" Text="Verklaringen concept" Style="{StaticResource TreeviewHeaderStyle}"/>
</Border>
<TreeView x:Name="MyTreeview" ItemsSource="{Binding}" Style="{StaticResource TreeviewStyle}">
<TreeView.Resources>
<ResourceDictionary Source="..\Themes\TreeviewItemStyle.xaml" />
</TreeView.Resources>
</TreeView>
<StackPanel.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:MyDataType}" ItemsSource="{Binding MyChildDataType}">
<StackPanel Orientation="Horizontal" acb:CommandBehavior.Event="MouseDown" acb:CommandBehavior.Command="{Binding SomeCommand}" acb:CommandBehavior.CommandParameter="Hi There">
And in the Viewmodel I added:
Public Property SomeCommand() As ICommand
Get
Return _someCommand
End Get
Private Set(value As ICommand)
_someCommand = value
End Set
End Property
Public Sub New()
MyBase.New()
Dim simpleCommand As SimpleCommand = New SimpleCommand()
simpleCommand.ExecuteDelegate = Sub(x As Object)
Dim test As String
test= "noot" 'I want to hit this breakpoint
End Sub
Me.SomeCommand = simpleCommand
End Sub
Who can help me out with the binding?
Regards,
Michel

The not too descriptive exception is throw because this binding is broken: acb:CommandBehavior.Command="{Binding SomeCommand}".
So WPF could not find your SomeCommand property. I guess the problem is around the HierarchicalDataTemplate so the DataContextis not what you would expect...
Check for binding errors in the Visual Studio's Output window during runtime and you will know what to fix then it should work.

Related

Wpf UserControl with its own data context and external dependency property

I'm trying to create a simple AudioPlayer control multiple reuse in a solution I'm working on. I have seen numerous example in various posts and blogs around the net and from those have created a small control with four buttons.
The xaml is defined thus:
<UserControl x:Class="AudioPlayer"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="30" d:DesignWidth="150">
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<Style TargetType="{x:Type Button}">
<Setter Property="Margin" Value="10,0,0,0" />
</Style>
</StackPanel.Resources>
<MediaElement Name="media" Source="{Binding Source}" LoadedBehavior="{Binding LoadedBehavior}"/>
<Button Width="24" Height="24" x:Name="Repeat" Background="Transparent" BorderBrush="Transparent">
<Image Source="Images/button_blue_repeat.png" ToolTip="Repeat"/>
</Button>
<Button Width="24" Height="24" x:Name="Play" Background="Transparent" BorderBrush="Transparent">
<Image Source="Images/button_blue_play.png" ToolTip="Play"/>
</Button>
<Button Width="24" Height="24" x:Name="Pause" Background="Transparent" BorderBrush="Transparent">
<Image Source="Images/button_blue_pause.png" ToolTip="Pause"/>
</Button>
<Button Width="24" Height="24" x:Name="Stop" Background="Transparent" BorderBrush="Transparent">
<Image Source="Images/button_blue_stop.png" ToolTip="Stop"/>
</Button>
</StackPanel>
With fairly simple code in the background;
Public Class AudioPlayer
Public Sub New()
InitializeComponent()
DataContext = New AudioPlayerViewModel With {.MediaElement = media, .Source = "bag1.mp3", .LoadedBehavior = MediaState.Manual, .CanCommandExecute = True}
End Sub
End Class
Public Class AudioPlayerViewModel
Inherits DependencyObject
Public Sub New()
Me.MediaCommand = New MediaElementCommand(Me)
End Sub
Public Property MediaElement() As MediaElement
Public Property Source() As String
Public Property LoadedBehavior() As MediaState
Public Property CanCommandExecute() As Boolean
Public Property MediaCommand() As ICommand
End Class
Public Class MediaElementCommand
Implements ICommand
Private vm As AudioPlayerViewModel
Public Sub New(ByVal vm As AudioPlayerViewModel)
Me.vm = vm
End Sub
Public Function CanExecute(ByVal parameter As Object) As Boolean Implements ICommand.CanExecute
Return vm.CanCommandExecute
End Function
Public Custom Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged
AddHandler(ByVal value As EventHandler)
AddHandler CommandManager.RequerySuggested, value
End AddHandler
RemoveHandler(ByVal value As EventHandler)
RemoveHandler CommandManager.RequerySuggested, value
End RemoveHandler
RaiseEvent(ByVal sender As System.Object, ByVal e As System.EventArgs)
End RaiseEvent
End Event
Public Sub Execute(ByVal parameter As Object) Implements ICommand.Execute
Dim action As String = DirectCast(parameter, String)
Select Case action.ToLower()
Case "play"
vm.MediaElement.Position = TimeSpan.Zero
vm.MediaElement.Play()
Case "stop"
vm.MediaElement.Stop()
Case "pause"
vm.MediaElement.Pause()
Case "resume"
vm.MediaElement.Play()
Case Else
Throw New NotSupportedException(String.Format("Unknown media action {0}", action))
End Select
End Sub
End Class
My question quite simply is this. From the code you can see that at present the sound that is being played is hard coded. What I would like to know is wheteher it would be possible to create a dependency property for this control (I presume it would be of type string to represent a path to a sound file but I'm not sure) so that when the control is created in other controls or windows their viewmodels can pass a sound property to it (if that makes sense!).
If it is possible where should I create it in respect of the code snippets shown?
Many thanks
You could create a DP, but it would not work the way users would expect.
For example, if the user were to write
<local:AudioPlayer Media="{Binding SomeString}" />
Then WPF tries to set Media = DataContext.SomeString
But since you have hardcoded DataContext = New AudioPlayerViewModel in the constructor, then the binding will most likely fail because users will be expecting their inherited DataContext to be used by the UserControl, but the hardcoded DataContext will be used instead.
It is always my advice to never hardcode the DataContext property inside of a UserControl. It breaks the entire WPF design pattern of having separate layers for UI and Data.
Either build a UserControl specifically for use with a specific Model or ViewModel being used as the DataContext, such as this :
<!-- Draw anything of type AudioPlayerViewModel with control AudioPlayer -->
<!-- DataContext will automatically set to the AudioPlayerViewModel -->
<DataTemplate DataType="{x:Type local:AudioPlayerViewModel}}">
<local:AudioPlayer />
</DataTemplate>
Or build it with the expectation that the DataContext can be absolutely anything, and DependencyProperites will be used to give the control the data it needs :
<!-- DataContext property can be anything, as long as it as the property MyString -->
<local:AudioPlayer Media="{Binding MyString}" />
The easiest way to get your code to work would probably be
Create the ViewModel as a private property instead of assiging it to the UserControl.DataContext
Bind or set the DataContext of the top level child inside your UserControl to your private property (in your case, the StackPanel)
Adjust the binding for your MediaElement to read from a custom DependencyProperty instead of from StackPanel.DataContext
Something like this :
<UserControl x:Name="MyAudioPlayer" ...>
<StackPanel x:Name="AudioPlayerRoot">
...
<MediaElement Source="{Binding ElementName=MyAudioPlayer, Path=MediaDependecyProperty}" ... />
...
</StackPanel>
</UserControl>
Public Sub New()
InitializeComponent()
AudioPlayerRoot.DataContext = New AudioPlayerViewModel ...
End Sub

Display text with a string in Textblock xaml

I have a textblock, and I want to display a text with a defined string. How to do it?
The textblock:
<TextBlock HorizontalAlignment="Left" TextWrapping="Wrap" Padding="6" VerticalAlignment="Center" Margin="45,0,0,0" Height="30" Width="386" Text="My Program ver. Version"/>
My string:
Public Version As String = "1.0a"
You can use StringFormat.
<TextBlock Text="{Binding Path=Version, StringFormat=My Program ver. {0}}" />
In your code- behind you must change Version to property (this property should be ReadOnly because it isn't change in runtime) and assign DataContext in constructor:
Class MainWindow
Public Sub New()
InitializeComponent()
Me.DataContext = Me
End Sub
ReadOnly Property Version As String
Get
Return "1.0a"
End Get
End Property
End Class
If you want your TextBlock to update the version number every time you have a new version,
you can do it like this in C#. You can probably find out easily how to write this in VB.
This will update your TextBlock everytime you publish a new version of your program.
In XAML you bind a TextBlock text to "Version":
<TextBlock Text="{Binding Version, Mode=OneWay}" />`
And then in code-behind or in your view-model you can use a property for that Binding that you have in the XAML TextBlock:
public string Version
{
get
{
return String.Format("VERSION: {0}",DeploymentInfo.Version.ToString());
}
}
Then you need to add a reference to "System.Deployment" in your project.
This will only work when you have done a "PUBLISH" of your project. When you start the debugger you will probably only see version number: 0.0.0.0
In XAML file:
First you should name your TextBlock i have gave tbWithNoName for example.
<TextBlock x:Name="tbWithNoName" HorizontalAlignment="Left" TextWrapping="Wrap" Padding="6" VerticalAlignment="Center" Margin="45,0,0,0" Height="30" Width="386" Text="My Program ver. Version"/>
Then add Loaded call on Window object.
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
Loaded="Window_Loaded">
Insert Window_Loaded function to your vb file.
Private Sub Window_Loaded(sender As Object, e As RoutedEventArgs)
tbWithNoName.Text = tbWithNoName.Text + " " + Version
End Sub
This will change TextBlock's Text when Window loaded

Selecteditem on combobox null reference exception

I want to use SelectedItem to set selection to a combobox from code.
I can only get it to work by using SelectedValue. SelectedItem will throw a null reference exception with this at the top of the stacktrace:
at AttachedCommandBehavior.CommandBehaviorBinding.Execute()
The XAML:
<Window x:Class="MainWindowView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:acb="clr-namespace:AttachedCommandBehavior;assembly=AttachedCommandBehavior"
Title="Window1" Height="300" Width="300">
<StackPanel>
<ComboBox Name="ComboItems1"
DisplayMemberPath="Value"
SelectedValuePath="Key"
ItemsSource="{Binding Items}"
SelectedValue="{Binding SelectedValue}"
acb:CommandBehavior.Event="SelectionChanged"
acb:CommandBehavior.Command="{Binding Path=SelectionChangedCommand}"
acb:CommandBehavior.CommandParameter="{Binding ElementName=ComboItems1, Path=SelectedItem}" />
<ComboBox Name="ComboItems2"
DisplayMemberPath="Value"
SelectedValuePath="Key"
ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem}"
acb:CommandBehavior.Event="SelectionChanged"
acb:CommandBehavior.Command="{Binding Path=SelectionChangedCommand}"
acb:CommandBehavior.CommandParameter="{Binding ElementName=ComboItems2, Path=SelectedItem}"/>
</StackPanel>
The code:
Imports AttachedCommandBehavior
Public Class MainWindowViewModel
Private _mainWindowView As MainWindowView
Public Property Items As New List(Of KeyValuePair(Of Integer, String))
Public Property SelectedItem As Nullable(Of KeyValuePair(Of Integer, String))
Public Property SelectedValue As Nullable(Of Integer)
Public Property SelectionChangedCommand As ICommand
Public Sub New()
Items.Add(New KeyValuePair(Of Integer, String)(1, "first item"))
Items.Add(New KeyValuePair(Of Integer, String)(2, "second item"))
Items.Add(New KeyValuePair(Of Integer, String)(3, "third item"))
Dim simpleCommand As SimpleCommand = New SimpleCommand()
simpleCommand.ExecuteDelegate = Sub(selectedItem As Object)
HandleSelectionChanged(selectedItem)
End Sub
SelectionChangedCommand = simpleCommand
SelectedValue = 1
'SelectedItem = Items(1) 'uncomment this to raise the null ref exception
End Sub
Private Sub HandleSelectionChanged(ByRef selectedItem As Object)
If selectedItem IsNot Nothing Then
'Do something
End If
End Sub
End Class
Why does selecteditem not work?
UPDATE:
Nikolay: you have a keen eye. That was due to last minute copy paste work!
Blindmeis: this, ofcourse, is an abstract from a much larger program in which I need the selectionchanged event to execute some actions. Those commandbindings have to stay (though maybe they need some fixing).
Regards,
Michel
why you have these commandbindings?
<ComboBox
DisplayMemberPath="Value"
SelectedValuePath="Key"
ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem}" />
viewmodel
//this select the "third item" in your combobox
SelectedItem = Items[2];/dont know the vb indexer stuff ;)
this works.
Edit:
viewmodel
public KeyValuePair<int, string> SelectedItem
{
get{return this._selectedItem;}
set{
if(this._selectedItem==value)
return;//no selection change
//if you got here then there was a selection change
this._selectedItem=value;
this.OnPropertyChanged("SelectedItem");
//do all action you want here
//and you do not need selection changed event commmandbinding stuff
}
}
acb:CommandBehavior.CommandParameter="{Binding ElementName=ComboItems, Path=SelectedItem}"
You don't have element with name ComboItems, you have ComboItems1 and ComboItems2. I think this is the problem.

wpf two way binding not working

i have
<Grid Name="thisPage">
<TextBlock Name="tbtb" />
<ScrollViewer Name="sv4" Visibility="Hidden">
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Title, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" TextChanged="TextBox_TextChanged"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
in the MainWindow.vb, i have
movieArray as ObservableCollection(of Movie)
For i As Integer = 0 To 5
Me.movieArray.Add(New Movie(i))
Next
Me.sv4.DataContext = Me.movieArray
Me.listBox5.DataContext = Me.movieArray
Private Sub TextBox_TextChanged(sender As System.Object, e As System.Windows.Controls.TextChangedEventArgs)
Me.tbtb.Text = ""
For Each m As Movie In movieArray
Me.tbtb.Text += p.Title.ToString + " ^ "
Next
End Sub
Class Movie
Implements INotifyPropertyChanged
Public Event PropertyChanged As PropertyChangedEventHandler _
Implements INotifyPropertyChanged.PropertyChanged
Private Sub NotifyPropertyChanged(ByVal info As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(info))
End Sub
Property Title As Integer
Get
Return Me._title
End Get
Set(value As Integer)
Me._title = value
If Not (value = _title) Then
Me._title= value
NotifyPropertyChanged("Title")
End If
End Set
End Property
for the next page i have,
<Grid Name="nextPage" Visibility="Hidden" >
<ListBox Name="listBox5" >
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ListBox>
</Grid >
To change pages i just toggle the visibility of thisPage and nextPage using back, next buttons.
IM not sure what im doing wrong as:-
listbox5 shows only the original values, not anything changed by
textboxes.
tbtb, however is able to update its values
I think the problem might be your 'Title' property setter.
I'm a C# guy, not a VB expert... but it would appear that NotifyPropertyChanged will never get called.
value = _title will always be true because you just set Me._title = value in the previous line of code. Thus you will never execute any of the code in your if statement.
Why are you using Textchanged evetn in two way binding you dont need kind of stuff. two way binding is directly bind values from your view to property and from property to view
so don't use textchanged event and try again. this will work.

WPF DataGrid and Avalon TimePicker binding is not working

I'm using a the WPF DataGrid from the wpf toolkit and a TimePicker from AvalonControlsLibrary to insert a collection of TimeSpans. My problem is that bindings are not working inside the DataGrid, and I have no clue of why this isn't working.
Here is my setup:
I have the following XAML:
<Window x:Class="TestMainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:wpf="http://schemas.microsoft.com/wpf/2008/toolkit" xmlns:a="http://schemas.AvalonControls/AvalonControlsLibrary/Controls" SizeToContent="WidthAndHeight" MinHeight="250" MinWidth="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<GroupBox Grid.Row="0">
<GroupBox.Header>
Testing it:
</GroupBox.Header>
<wpf:DataGrid ItemsSource="{Binding Path=TestSpans}" AutoGenerateColumns="False">
<wpf:DataGrid.Columns>
<wpf:DataGridTemplateColumn Header="Start">
<wpf:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<a:TimePicker SelectedTime="{Binding Path=Span, Mode=TwoWay}" />
</DataTemplate>
</wpf:DataGridTemplateColumn.CellEditingTemplate>
<wpf:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Span}" />
</DataTemplate>
</wpf:DataGridTemplateColumn.CellTemplate>
</wpf:DataGridTemplateColumn>
</wpf:DataGrid.Columns>
</wpf:DataGrid>
</GroupBox>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Grid.Row="1">
<a:TimePicker SelectedTime="{Binding Path=SelectedTime, Mode=TwoWay}" />
</StackPanel>
</Grid>
And this is my ViewModel:
Imports System.Collections.ObjectModel
Public Class TestMainWindowViewModel
Private _selectedTime As TimeSpan = DateTime.Now.TimeOfDay
Public Property SelectedTime() As TimeSpan
Get
Return _selectedTime
End Get
Set(ByVal value As TimeSpan)
_selectedTime = value
End Set
End Property
Private _testSpans As ObservableCollection(Of TimeSpanContainer) = New ObservableCollection(Of TimeSpanContainer)
Public Property TestSpans() As ObservableCollection(Of TimeSpanContainer)
Get
Return _testSpans
End Get
Set(ByVal value As ObservableCollection(Of TimeSpanContainer))
_testSpans = value
End Set
End Property
Public Sub New()
_testSpans.Add(DateTime.Now.TimeOfDay)
_testSpans.Add(DateTime.Now.TimeOfDay)
_testSpans.Add(DateTime.Now.TimeOfDay)
End Sub
End Class
Public Class TimeSpanContainer
Private _span As TimeSpan
Public Property Span() As TimeSpan
Get
Return _span
End Get
Set(ByVal value As TimeSpan)
_span = value
End Set
End Property
Public Sub New(ByVal t As TimeSpan)
_span = t
End Sub
End Class
I'm starting this window in application.xaml.vb like this:
Class Application
' Application-level events, such as Startup, Exit, and DispatcherUnhandledException
' can be handled in this file.
Protected Overrides Sub OnStartup(ByVal e As System.Windows.StartupEventArgs)
MyBase.OnStartup(e)
Dim window As TestMainWindow = New TestMainWindow
window.DataContext = New TestMainWindowViewModel()
window.Show()
End Sub
End Class
EDIT 1: I forgot to mention that the binding to SelectedTime TimeSpan works as expected. The problem are the bindings inside the DataGrid.
EDIT 2: Changed example a little bit to show the problem better.
What do you mean by your bindings aren't working? Are you getting no value in the timepicker control when you attempt to edit the value?
Edit:
Ok, I was having this same issue yesterday and I think there is 2 parts to the issue.
If there is no value appearing in the TimePicker control when you switch to edit mode then there is probably a binding issue with the control.
The binding to the underlying value I found to be an issue with using the DataGridTemplateColumn. Basically the grid doesnt handle your databinding back using the same mechanisms of regular bound columns. What it means is you need to perform the following binding on your controls within the column:
SelectedTime="{Binding Span, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
This will fix the binding back to the underlying object. However if there is still an issue with the control it may not help you much. Sorry but I haven't used AvalonControlsLibrary so not sure if there is a potential problem there. Fixing step 2 solved my issues.
Cheers
-Leigh
I know this is an old question, but I where playing with this exact control having the exact same issue. I looked in the TimePicker class of AvalonControlsLibrary and the constructor looks like this
/// <summary>
/// Default constructor
/// </summary>
public TimePicker()
{
SelectedTime = DateTime.Now.TimeOfDay;
}
Removing the line setting the SelectedTime restored the databinding behaviour for me making your posted example work as intended.

Resources