WPF binding backgrounds as SolidColorBrush - wpf

I have this grid:
<Grid x:Name="topGrid" Height="100" VerticalAlignment="Top" Margin="10,0,0,0" />
In my code if I set the background like this:
topGrid.Background = "#FF3C3C3C".ToBrush()
Using this extension:
Module Extensions
<Extension()>
Function ToBrush(ByVal HexColorString As String) As SolidColorBrush
Return CType((New BrushConverter().ConvertFrom(HexColorString)),
SolidColorBrush)
End Function
End Module
I can change background just fine, but i have around 20 grids on my form and i want to change backgrounds of all grids at once using bindings.
I have tried doing it like this:
This is xml:
<Grid x:Name="topGrid" Background="{Binding MyBackgroundColor}" Height="100" VerticalAlignment="Top" Margin="10,0,0,0" >
This is code:
Private Sub button1_Click(sender As Object, e As RoutedEventArgs) Handles button1.Click
MyBackgroundColor = "#FF3C3C3C".ToBrush()
End Sub
Private _myBackgroundColor As SolidColorBrush
Public Property MyBackgroundColor() As SolidColorBrush
Get
Return _myBackgroundColor
End Get
Set
_myBackgroundColor = Value
End Set
End Property
Public Sub New()
InitializeComponent()
End Sub

If you want to change all the backgrounds on many grids then a style is another way to go.
Although this is c# there's very little code and you could run it through an online converter.
I've done this in app.xaml for quickness but you'd want to put it in a resource dictionary merged in app.xaml in a proper app.
<Application.Resources>
<SolidColorBrush x:Key="gridBackgroundBrush" Color="Blue"/>
<Style TargetType="{x:Type Grid}">
<Setter Property="Background" Value="{DynamicResource gridBackgroundBrush}"/>
</Style>
</Application.Resources>
</Application>
And you can change that brush:
private void Button_Click_1(object sender, RoutedEventArgs e)
{
Color colour = (Color)ColorConverter.ConvertFromString("#FFD700");
Application.Current.Resources["gridBackgroundBrush"] = new SolidColorBrush(colour);
}
If you don't want one or two grids to have this behaviour you could just set their background to white or transparent and this will have precedence over the style.
If your requirement is instead more complicated then you could lose the style and instead use the resource directly as DynamicResource. Which might be what Clemens means.
<Grid Background="{DynamicResource gridBackgroundBrush}"

Related

How do I find the image in a WPF Datagrid ColumnHeader so I can change the image?

I'm attempting to implement Excel-like column filtering and sorting. To do this, I used a DataTemplate to define the Column Header.
<DataGrid x:Name="dataGrid" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" CanUserSortColumns="False">
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="23"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button x:Name="ExcelFilterButton" Tag="{Binding}" Click="ExcelFilterButton_Click" Margin="0,0,0,0" BorderThickness="0" Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}" Focusable="False" Grid.Column="0">
<Image Source="Resources\NoSortNoFilter.png" Width="19" Height="19" />
</Button>
<TextBlock Text="{Binding}" HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Column="1" />
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.Resources>
</DataGrid>
And it comes out nicely.
I tried using VisualTreeHelper to find the image from the Column Header, but the Header property is a string. I've tried using the HeaderStyle and HeaderTemplate properties also but to no avail.
Using a WPF Spy program called Snoop, I can see the image in there, but still can't figure out how to access it in code. The reason I need to access it in code is to change the image based on whether that column is sorted and/or filtered. (Could this be done in XAML?)
Ok, I figured out how to do it. This most likely not the right way to do it, but I found a way that works.
To give you a little about the process.
The user clicks a header button. The buttons Tag property is bound to the column header.
The click event handler instantiates the context menu and sets its Tag to equal the button Tag.
The user clicks on a menu item.
The event handler sends the Context Menu Tag property and image name to the routine that finds the button, and then the image in the button, and changes the image.
now for the code.
The button click event handler:
Private Sub ExcelFilterButton_Click(sender As Object, e As RoutedEventArgs)
With DirectCast(Resources("sortContextMenu"), ContextMenu)
.Tag = DirectCast(sender, Button).Tag
.IsOpen = True
End With
End Sub
The menu item click event handler
Private Sub ContextMenuItem_Click(Sender As Object, e As RoutedEventArgs)
If TypeOf Sender Is MenuItem Then
'just testing, of course this isn't all this handler does.
SetColumnSortImage(Sender.Tag, "Filtered")
End If
End Sub
The SetColumnSortImage routine, which calls the two following routines.
Private Sub SetColumnSortImage(Tag As String, ImageName As String)
Dim btn As Button = Nothing
GetSortButton(Of Button)(dataGrid, Tag, btn)
If btn IsNot Nothing Then
Dim img As Image = GetChildOfType(Of Image)(btn)
img.Source = New BitmapImage(New Uri("pack://application:,,,/Resources/" & ImageName & ".png"))
End If
End Sub
The GetSortButton routine
Private Sub GetSortButton(Of T As DependencyObject)(dep As DependencyObject, Tag As String, ByRef out As DependencyObject)
If dep IsNot Nothing Then
If TypeOf dep Is Button AndAlso CType(dep, Button).Tag = Tag Then
out = dep
Else
If VisualTreeHelper.GetChildrenCount(dep) > 0 Then
For i As Integer = 0 To VisualTreeHelper.GetChildrenCount(dep) - 1
GetSortButton(Of T)(VisualTreeHelper.GetChild(dep, i), Tag, out)
Next
End If
End If
End If
End Sub
This routine was found elsewhere on StackOverflow in C#. I converted it to VB.
Private Function GetChildOfType(Of T As DependencyObject)(depObj As DependencyObject) As T
If depObj Is Nothing Then
Return Nothing
End If
For i As Integer = 0 To VisualTreeHelper.GetChildrenCount(depObj) - 1
Dim child = VisualTreeHelper.GetChild(depObj, i)
Dim result = If(TryCast(child, T), GetChildOfType(Of T)(child))
If result IsNot Nothing Then
Return result
End If
Next
Return Nothing
End Function
You may have a better way. Please post if you do.

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

WPF: Placing a textbox randomly on a canvas

I have a canvas of sorts derived from a 'Panel' used for custom drawing of lines and other geometries, all from VB code. I got this approach from a book and I'm not sure it's the best approach. The drawing part works so far for me.
But what I need is to put a textbox control on the control containing text which can be edited by the user. The textbox needs to be placed at coordinates determined dynamically and later deleted. There will probably be other controls handled so.
The following code does nothing:
tb = New TextBox()
tb.Text = "How now brown cow?"
tb.BorderThickness = New Thickness(3)
tb.BorderBrush = Brushes.CadetBlue
drawingSurface.Children.Add(tb)
This is the definition of my DrawingCanvas:
Public Class DrawingCanvas
Inherits Panel
Private visuals As New List(Of Visual)()
Private hits As New List(Of DrawingVisual)()
Protected Overrides Function GetVisualChild(ByVal index As Integer) As Visual
Return visuals(index)
End Function
Protected Overrides ReadOnly Property VisualChildrenCount() As Integer
Get
Return visuals.Count
End Get
End Property
Public Sub AddVisual(ByVal visual As Visual)
visuals.Add(visual)
MyBase.AddVisualChild(visual)
MyBase.AddLogicalChild(visual)
End Sub
Public Sub DeleteVisual(ByVal visual As Visual)
visuals.Remove(visual)
MyBase.RemoveVisualChild(visual)
MyBase.RemoveLogicalChild(visual)
End Sub
Public Function GetVisual(ByVal point As Point) As DrawingVisual
Dim hitResult As HitTestResult = VisualTreeHelper.HitTest(Me, point)
Return TryCast(hitResult.VisualHit, DrawingVisual)
End Function
Public Function GetVisuals(ByVal region As Geometry) As List(Of DrawingVisual)
hits.Clear()
Dim parameters As New GeometryHitTestParameters(region)
Dim callback As New HitTestResultCallback(AddressOf Me.HitTestCallback)
VisualTreeHelper.HitTest(Me, Nothing, callback, parameters)
Return hits
End Function
Private Function HitTestCallback(ByVal result As HitTestResult) As HitTestResultBehavior
Dim geometryResult As GeometryHitTestResult = CType(result, GeometryHitTestResult)
Dim visual As DrawingVisual = TryCast(result.VisualHit, DrawingVisual)
If visual IsNot Nothing AndAlso geometryResult.IntersectionDetail = IntersectionDetail.FullyInside Then
hits.Add(visual)
MsgBox("Ouch")
End If
Return HitTestResultBehavior.Continue
End Function
End Class
Here is the XAML. I added a textbox to the DrawingCanvas just to see if something appears. Nothing did. In fact, I want to do this in code, not XAML. I thought I could hide or move it around dynamically.
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Music"
Title="MainWindow" Height="539" Width="892">
<DockPanel>
<Menu DockPanel.Dock="Top" Name="MainMenu" VerticalAlignment="Top" Height="25">
<MenuItem Name="File" Header="File">
<MenuItem Name="Open" Header="Bla bla..."/>
</MenuItem>
</Menu>
<local:DrawingCanvas DockPanel.Dock="Bottom" x:Name="drawingSurface" RenderTransformOrigin="0.5,0.5" >
<TextBox Height="0" Name="TextBox1" Width="45" Text="How now brown cow?" />
</local:DrawingCanvas>
</DockPanel>
</Window>
Thanks for helping a nooby. A solution would be very useful for me. This was easy with windows forms, but I need the drawing speed of WPF.
I think you are a bit off here. In WPF you have a control called Canvas. I would suggest you use that instead of your own "DrawingCanvas", which I can't get to work btw. :( (For some reason I cant create code blocks so if someone can edit it I would be pleased)
Anyhow,
<local:DrawingCanvas DockPanel.Dock="Bottom" x:Name="drawingSurface" RenderTransformOrigin="0.5,0.5" >
<TextBox Height="0" Name="TextBox1" Width="45" Text="How now brown cow?" />
</local:DrawingCanvas>
Turns into:
<Canvas x:Name="drawingSurface">
</Canvas>
And then to add a textbox just do as your current code:
Dim tb as New TextBox
drawingSurface.Children.Add(tb)
This should give you what you need.
Heres the code for adding a rectangle to your canvas.
Private Sub DrawBackground()
Dim Rect As New Rectangle()
Rect.Height = 50
Rect.Width = 50
Rect.Fill = Brushes.Cornsilk
drawingSurface.SetTop(Rect, 30)
drawingSurface.SetLeft(Rect, 100)
drawingSurface.Children.Add(Rect)
End Sub
I'll add another answer that might be more in the line of what you are looking for. This is a class that inherits from Canvas that will allow you to draw stuff in the same way as say you do in your comment.
I also creates a textbox on a random location when it is created.
Public Class DrawingCanvas
Inherits Canvas
Public RandomTextBox As New TextBox
Protected Overrides Sub OnRender(dc As System.Windows.Media.DrawingContext)
Dim brush As Brush = Brushes.Black
Dim drawingPen As Pen = New Pen(Brushes.Green, 3)
dc.DrawRectangle(brush, drawingPen, New Rect(5, 5, Me.ActualWidth - 5, Me.ActualHeight - 5))
RandomTextBox.Text = "Herpdiderp"
If Not Me.Children.Contains(RandomTextBox) Then
Dim r As New Random()
RandomTextBox.Height = 23
RandomTextBox.Width = 100
Me.SetTop(RandomTextBox, r.Next(0, Me.ActualHeight - RandomTextBox.Height))
Me.SetLeft(RandomTextBox, r.Next(0, Me.ActualWidth - RandomTextBox.Width))
Me.Children.Add(RandomTextBox)
End If
End Sub
End Class
This is not a full answer. #WozzeC, you were right about using the canvas - almost.
I have managed to solve this in xaml alone - I want to eventually solve it in vb.net.
<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">
<Grid>
<DockPanel HorizontalAlignment="Stretch" Name="DockPanel1" VerticalAlignment="Stretch" >
<Menu Height="23" DockPanel.Dock="Top" Name="Menu1" VerticalAlignment="Top" />
<Canvas Name="Canvas1" Background="Aquamarine">
<TextBox Canvas.Left="118" HorizontalScrollBarVisibility="Disabled" Canvas.Top="81" AcceptsReturn="True" Height="auto" Name="TextBox1" Width="68" Text="Herpdiderp" BorderThickness="0" Background="Aquamarine" />
</Canvas>
</DockPanel>
</Grid>
And here is a piece of code that expands the text as needed. I think it's almost totally cool. It expands both to the right and downwards, as if you're actually typing on the form. It adds a little too much on the right, but it's not visible in this version because the background color is the same.
Here is the event code that expands it to the right.
Imports System.Globalization
Class MainWindow
Private Sub TextBox1_TextChanged(sender As System.Object, e As System.Windows.Controls.TextChangedEventArgs) Handles TextBox1.TextChanged
Dim ft As New FormattedText(TextBox1.Text, CultureInfo.GetCultureInfo("en-us"), FlowDirection.LeftToRight, New Typeface("Verdana"), 16, Brushes.Black)
TextBox1.Width = ft.Width
End Sub
End Class
I tried this with my existing solution and the textbox does not appear. I made the DrawingCanvas into a plain Canvas and commented out all the code referring to the DrawingCanvas. And the textbox does appear. The problem is this: I need the functionality in the DrawingCanvas - which derives from Canvas. But because the baseclass methods are Protected, I can't get to them. I can only use them in a derived class, unless there is another way I don't know about.
Any ideas about how to solve this?

How to Create a DialogBox to prompt the user for Yes/No option in WPF

I know how to do this on Windows Form App, but I couldn't find anyway of doing so on a WPF App.
How would I present the user a blocking DialogBox with Yes/No option and get/process the response from the user?
Here's an example:
string sMessageBoxText = "Do you want to continue?";
string sCaption = "My Test Application";
MessageBoxButton btnMessageBox = MessageBoxButton.YesNoCancel;
MessageBoxImage icnMessageBox = MessageBoxImage.Warning;
MessageBoxResult rsltMessageBox = MessageBox.Show(sMessageBoxText, sCaption, btnMessageBox, icnMessageBox);
switch (rsltMessageBox)
{
case MessageBoxResult.Yes:
/* ... */
break;
case MessageBoxResult.No:
/* ... */
break;
case MessageBoxResult.Cancel:
/* ... */
break;
}
Please note that while Radu's answer works, you cannot apply WPF styles to the MessageBox.
I took a different approach to this problem.
I created a class to serve as a View Model for my message window and I created a style for how I wanted my window to appear. Later in code I instantiated a new Window, set it's DataContext to an instance of my View Model, and set the Window's Style property to the style I created for the window.
I know it sounds a bit overkill, and I'm not sure how other people go about solving this same issue... but my solution is quite flexible and I'm starting to really like it.
For example, here is Dialog View Model:
Public Class MyDialogViewModel
Public Event Closed()
Public Property Message As String
Public Property Cancel As MyNamespace.RelayCommand
Public Property Close As MyNamespace.RelayCommand
Public Property WasCancelled As Boolean
Public Sub New()
WasCancelled = True
Cancel = New MyNamespace.RelayCommand(AddressOf CancelClicked)
Close = New MyNamespace.RelayCommand(AddressOf CloseClicked)
End Sub
Private Sub CancelClicked()
RaiseEvent Closed()
End Sub
Private Sub CloseClicked()
WasCancelled = False
RaiseEvent Closed()
End Sub
End Class
Here is my style for a basic "message" window:
<Style x:Key="myMessageStyle" TargetType="{x:Type myNameSpace:CustomDialogWindow}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<ControlTemplate.Resources>
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Width" Value="100"/>
<Setter Property="Height" Value="25"/>
</Style>
</ControlTemplate.Resources>
<Border >
<DockPanel Margin="10,10,0,10">
<TextBlock Text="{Binding Message}" Width="Auto" TextWrapping="WrapWithOverflow" DockPanel.Dock="Top"
Margin="10"
Foreground="{StaticResource MyMessageBoxForegroundColor}"/>
<DockPanel Margin="5,0,0,0" DockPanel.Dock="Bottom">
<Button Content="Ok" Command="{Binding Close}" ></Button>
<Button Content="Cancel" Command="{Binding Cancel}" HorizontalAlignment="Stretch"></Button>
</DockPanel>
</DockPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
My CustomDialogWindow is simply a window with nothing in it:
(XAML)
<Window x:Class="CustomDialogWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="CustomDialogWindow"
SizeToContent="WidthAndHeight">
</Window>
And in the CustomDialogWindow I have the following code so that the window closes when the user clicks cancel or ok:
Public Class CustomDialogWindow
Private Sub CustomDialogWindow_DataContextChanged(ByVal sender As Object, ByVal e As System.Windows.DependencyPropertyChangedEventArgs) Handles Me.DataContextChanged
Dim dContext As MyDialogViewModel= TryCast(DataContext, MyDialogViewModel)
If dContext IsNot Nothing Then
AddHandler DirectCast(DataContext, MyDialogViewModel).CloseWindow, AddressOf CloseWindow
End If
End Sub
Private Sub CloseWindow()
Me.Close()
End Sub
End Class
Now when I need to use the window I just instantiate a new CustomDialogWindow, set it's DataContext to a new instance of the DialogViewModel class, and set it's style to the "myMessageStyle":
Dim cdw As New CustomDialogWindow
Dim dvm As New DialogViewModel
dvm.Message = "Hello World!"
cdw.DataContext = dvm
cdw.ShowDialog()
If dvm.WasCancelled = False Then
'....'
End If
The reason why I like this approach is because I inherit from the MyDialogViewModel and provide more properties so that, for instance, I can display a bunch of options for the user to choose from. I just supply custom styles for each type of window I want to display (making sure to bind the appropriate properties). Like I said, it's very flexible and pretty simple to implement.
Cheers!
-Frinny

WPF Button in DataGrid/ListBox

I've encountered a problem when converting a WPF project from vs2008 to vs2010.
I have a DataGrid that contains a ListBox. Each ListBoxItem has a Label and a Button. After converting to vs2010 the button no longer renders but crashes the app as soon as it comes into view. (Ie. the app loads but when the ListBox is created I get a NullReferenceException. What does work though is to remove the click event from the button and then it renders fine :) Same type of setup with Button within ListBoxItem also works when not inside a DataGrid. The content of ListBox obviously is meant to be dynamic but when working with a static collection I get the same error. Also removing the CommandParam does not help at all. Any pointers most welcome.
Code:
<DataGrid x:Name="DgTest" AutoGenerateColumns="false">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel>
<ListBox ItemsSource="{Binding ItemList}">
<ListBox.ItemTemplate>
<DataTemplate >
<StackPanel Style="{StaticResource hzp}">
<Label />
<Button Click="Button_Click" Content="TestButton"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Code-behind:
Imports System.Collections.ObjectModel
Class MainWindow
Public TestList As New ObservableCollection(Of TestClass)
Private Sub MainWindow_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded
DgTest.ItemsSource = TestList
TestList.Add(New TestClass(0))
TestList.Add(New TestClass(1))
End Sub
Public Class TestClass
Private _ItemList As New List(Of String)
Private _id As Integer
Public Property ItemList() As List(Of String)
Get
Return _ItemList
End Get
Set(ByVal value As List(Of String))
_ItemList = value
End Set
End Property
Public Property Id() As Integer
Get
Return _id
End Get
Set(ByVal value As Integer)
_id = value
End Set
End Property
Public Sub New(ByVal id As Integer)
_ItemList.Add("String1")
_id = id
End Sub
End Class
Private Sub Button_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
End Sub
End Class
And in App Resources:
<Style TargetType="StackPanel" x:Key="hzp">
<Setter Property="Orientation" Value="Horizontal"/>
<Setter Property="Background" Value="Orange"/>
</Style>
Now here's the strange thing. If the Stackpanel Style is removed, the button will work. If the Click event for the button is removed, it will load normally.
Seems like your event handler is gone from the code-behind file, check that first. Comment if that is not the case.
I believe I have found the answer to my own question. In the ListBox bound to an ObservableCollection all Styles must be DynamicResource. Using StaticResource worked well in 3.5 but not 4! Took a few hours of randomly testing everything to find this. Case closed

Resources