I'm trying to make a DropdownMenu in WPF with the code-behind in VB. For any reason that I can't realise this DropdownMenu is not working as I would like to.
This is the MainWindow.xaml file:
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
mc:Ignorable="d" Height="600" Width="1080" Foreground="White" ResizeMode="NoResize" WindowStartupLocation="CenterScreen" WindowStyle="None">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="250"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<materialDesign:ColorZone Mode="PrimaryMid" Grid.ColumnSpan="2" HorizontalAlignment="Stretch">
<Grid>
<materialDesign:PopupBox PlacementMode="BottomAndAlignRightEdges" HorizontalAlignment="Right" Margin="10"/>
</Grid>
</materialDesign:ColorZone>
<Grid HorizontalAlignment="Stretch" Grid.Row="1" Background="{StaticResource PrimaryHueMidBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="70"/>
<RowDefinition Height="326*"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0" Background="GhostWhite">
<Image Source="Assets/logo.png"/>
</Grid>
<ScrollViewer HorizontalAlignment="Stretch" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto" Grid.Row="1">
<StackPanel x:Name="Menu" Margin="10"/>
</ScrollViewer>
</Grid>
</Grid>
</Window>
This is the MainWindow.xaml.vb file:
Imports MaterialDesignThemes.Wpf
Imports Project.ViewModel
Partial Public Class MainWindow
Inherits Window
Public Sub New()
InitializeComponent()
Dim menuRegister = New List(Of SubItem)()
menuRegister.Add(New SubItem("Customer"))
menuRegister.Add(New SubItem("Providers"))
menuRegister.Add(New SubItem("Employees"))
menuRegister.Add(New SubItem("Products"))
Dim item6 = New ItemMenu("Register", menuRegister, PackIconKind.Register)
Dim menuSchedule = New List(Of SubItem)()
menuSchedule.Add(New SubItem("Services"))
menuSchedule.Add(New SubItem("Meetings"))
Dim item1 = New ItemMenu("Appointments", menuSchedule, PackIconKind.Schedule)
Dim menuReports = New List(Of SubItem)()
menuReports.Add(New SubItem("Customers"))
menuReports.Add(New SubItem("Providers"))
menuReports.Add(New SubItem("Products"))
menuReports.Add(New SubItem("Stock"))
menuReports.Add(New SubItem("Sales"))
Dim item2 = New ItemMenu("Reports", menuReports, PackIconKind.FileReport)
Dim menuExpenses = New List(Of SubItem)()
menuExpenses.Add(New SubItem("Fixed"))
menuExpenses.Add(New SubItem("Variable"))
Dim item3 = New ItemMenu("Expenses", menuExpenses, PackIconKind.ShoppingBasket)
Dim menuFinancial = New List(Of SubItem)()
menuFinancial.Add(New SubItem("Cash flow"))
Dim item4 = New ItemMenu("Financial", menuFinancial, PackIconKind.ScaleBalance)
Dim item0 = New ItemMenu("Dashboard", New UserControl(), PackIconKind.ViewDashboard)
Menu.Children.Add(New UserControlMenuItem(item0))
Menu.Children.Add(New UserControlMenuItem(item6))
Menu.Children.Add(New UserControlMenuItem(item1))
Menu.Children.Add(New UserControlMenuItem(item2))
Menu.Children.Add(New UserControlMenuItem(item3))
Menu.Children.Add(New UserControlMenuItem(item4))
End Sub
End Class
This is the ItemMenu Class:
Imports MaterialDesignThemes.Wpf
Namespace ViewModel
Public Class ItemMenu
Public Sub New(ByVal header As String, ByVal subItems As List(Of SubItem), ByVal icon As PackIconKind)
header = header
subItems = subItems
icon = icon
End Sub
Public Sub New(ByVal header As String, ByVal screen As UserControl, ByVal icon As PackIconKind)
header = header
screen = screen
icon = icon
End Sub
Public Property Header As String
Public Property Icon As PackIconKind
Public Property SubItems As List(Of SubItem)
Public Property Screen As UserControl
End Class
End Namespace
This is the SubItem Class:
Namespace ViewModel
Public Class SubItem
Public Sub New(ByVal name As String, ByVal Optional screen As UserControl = Nothing)
name = name
screen = screen
End Sub
Public Property Name As String
Public Property Screen As UserControl
End Class
End Namespace
This is the UserControlMenuItem.xaml file:
<UserControl x:Class="UserControlMenuItem"
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"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
mc:Ignorable="d" >
<Grid>
<materialDesign:PackIcon Kind="{Binding Path=Icon}" Width="15" Height="15" Margin="10 16" Foreground="White"/>
<ListBoxItem x:Name="ListViewItemMenu" Content="{Binding Path=Header}" Padding="37 14" FontSize="15" Foreground="White"/>
<Expander x:Name="ExpanderMenu" Header="{Binding Path=Header}" IsExpanded="False" Width="210" HorizontalAlignment="Right" Background="{x:Null}" Foreground="White">
<ListView x:Name="ListViewMenu" ItemsSource="{Binding Path=SubItems}" Foreground="White" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}" Padding="20 5"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Expander>
</Grid>
This is the UserControlMenuItem.xaml.vb file:
Imports Project.ViewModel
Partial Public Class UserControlMenuItem
Inherits UserControl
Public Sub New(ByVal itemMenu As ItemMenu)
InitializeComponent()
ExpanderMenu.Visibility = If(itemMenu.SubItems Is Nothing, Visibility.Collapsed, Visibility.Visible)
ListViewItemMenu.Visibility = If(itemMenu.SubItems Is Nothing, Visibility.Visible, Visibility.Collapsed)
Me.DataContext = itemMenu
End Sub
End Class
This is the Window when the app is running:
What I'm missing here?
Design cloned from DropDownMenu
I solved this problem. It was a detail I didn't realise. These are the corrected Classes:
The ItemMenu Class:
Imports MaterialDesignThemes.Wpf
Namespace ViewModel
Public Class ItemMenu
Public Sub New(ByVal header As String, ByVal subItems As List(Of SubItem), ByVal icon As PackIconKind)
Me.Header = header
Me.SubItems = subItems
Me.Icon = icon
End Sub
Public Sub New(ByVal header As String, ByVal screen As UserControl, ByVal icon As PackIconKind)
Me.Header = header
Me.Screen = screen
Me.Icon = icon
End Sub
Public Property Header As String
Public Property Icon As PackIconKind
Public Property SubItems As List(Of SubItem)
Public Property Screen As UserControl
End Class
End Namespace
The SubItem Class:
Namespace ViewModel
Public Class SubItem
Public Sub New(ByVal name As String, ByVal Optional screen As UserControl = Nothing)
Me.Name = name
Me.Screen = screen
End Sub
Public Property Name As String
Public Property Screen As UserControl
End Class
End Namespace
Just remained make reference to the Class with "Me" (self in other languages like Python) and then assign a value to the property:
Me.Header = header 'Example in ItemMenu Class
Me.Name = name 'Example in SubItem Class
Related
I want to write a code from UserControl's Button to set value to MainWindow's ColumnDefinition.
When clicked button I need change to this:
<ColumnDefinition Width="0" MaxWidth="400" MinWidth="10" x:Name="MainMenu" x:FieldModifier="public" />
Below is my code:
MainWindow.xaml
<Grid Margin="0,0,0,0">
<local:DockPanelTop />
<Grid Margin="5,45,5,25" x:Name="MainGrid" x:FieldModifier="public">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150" MaxWidth="400" MinWidth="10" x:Name="MainMenu" x:FieldModifier="public" />
<ColumnDefinition Width="5" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
</Grid>
DockPanelTop.xaml
<UserControl x:Class="DockPanelTop"
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"
xmlns:local="clr-namespace:WpfApp4"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<DockPanel LastChildFill="False" VerticalAlignment="Top" Background="Gray" Margin="0,0,0,0" Height="40">
<Button x:Name="HideMenu" Content="Hidden
Menu" Width="50" Margin="5" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" FontSize="9" />
</DockPanel>
</UserControl>
DockPanelTop.xaml.vb
Public Class DockPanelTop
Private Sub HideMenu_Click(sender As Object, e As RoutedEventArgs) Handles HideMenu.Click
("When clicked set value to columndefinition width to "0" ")
End Sub
End Class
I find nothing the code sample on web in VB to the DockPanelTop.xaml.vb :( Please help me.
Sry, bad English.TY.
You can get a reference to the parent window using the shared Window.GetWindow method. Try this:
Private Sub HideMenu_Click(sender As Object, e As RoutedEventArgs)
Dim window = TryCast(System.Windows.Window.GetWindow(Me), MainWindow)
If (window IsNot Nothing) Then
Dim columnDef = window.MainGrid.ColumnDefinitions(0)
Dim width = New GridLength(0)
columnDef.Width = New GridLength(0)
columnDef.MinWidth = 0.0
End If
End Sub
My Answer:
Public Sub HideMenu_Click(sender As Object, e As RoutedEventArgs) Handles HideMenu.Click
Dim window = TryCast(System.Windows.Window.GetWindow(Me), MainWindow)
Dim columnDef0 = window.MainGrid.ColumnDefinitions(0)
Dim columnDef0Width = window.MainGrid.ColumnDefinitions(0).Width
If (window IsNot Nothing) Then
If columnDef0Width.Value <= 10 Then
columnDef0.Width = New GridLength(150)
columnDef0.MinWidth = 10.0
ElseIf columnDef0.Width.Value > 10 Then
columnDef0.Width = New GridLength(10)
columnDef0.MinWidth = 10.0
End If
End If
End Sub
About two weeks ago I started to develop in WPF and, since I only developed in WinForms, I ran into common problems but managed to find the solution for them. However, currently I'm stuck at something: adding items with multiple columns (via visual basic code, not xaml) into a listview.
I'm not sure if it's best to use a Listview or a DataGrid control for this but basically I want the user to be able to add favourite songs to a list, save them to a file and load the file whenever the app is opened. The listview currently has three columns: Artist, Song and Status.
When I was programming in WinForms, I used to do this:
Dim Song As New ListViewItem
Form1.ListView1.Items.Add(Song)
Song.Text = TextBox1.Text
Song.SubItems.Add(TextBox2.Text)
Song.SubItems.Add(TextBox3.Text)
Then, to save:
Dim Song As New ListViewItem
Form1.ListView1.Items.Add(Song)
Song.Text = TextBox1.Text
Song.SubItems.Add(TextBox2.Text)
Song.SubItems.Add(TextBox3.Text)
Try
Dim myWriter As New IO.StreamWriter(PATH_DATABASE)
For Each myItem As ListViewItem In Form1.ListView1.Items
myWriter.WriteLine(myItem.Text & "|" & myItem.SubItems(1).Text & "|" & myItem.SubItems(2).Text & "|" & myItem.SubItems(3).Text
Next
myWriter.Close()
Catch ex As Exception
MsgBox("Error: " & ex.Message, vbCritical, "Error")
End Try
I've been searching and I've found that I need to use binding so I've tried this:
Imports System
Imports System.Collections.Generic
Imports System.Collections.ObjectModel
Imports System.Windows
Public Structure Song
Public _artist As String
Public _title As String
Public _status As String
Property Artist() As String
Get
Return _artist
End Get
Set(ByVal Value As String)
_artist = Value
End Set
End Property
Property Title() As String
Get
Return _title
End Get
Set(ByVal Value As String)
_title = Value
End Set
End Property
Property Status() As String
Get
Return _status
End Get
Set(ByVal Value As String)
_status = Value
End Set
End Property
End Structure
Public Class WINDOW_AddSong
Dim songs As New ObservableCollection(Of Song)
Private Sub Button1_Click(sender As Object, e As RoutedEventArgs) Handles Button1.Click
Dim Song As New ListViewItem
For Each wnd As Window In Application.Current.Windows
If wnd.GetType Is GetType(MainWindow) Then
DirectCast(wnd, MainWindow).Listview1.Items.Add(Alimento)
Alimento.Content = New Song() With {._artist = "Lol", ._title = "Lol2", ._status = "Lol3"}
End If
Next
End Sub
End Class
And in the XAML listview:
<GridViewColumn Header="Artist" DisplayMemberBinding="{Binding Artist}"/>
<GridViewColumn Header="Title" DisplayMemberBinding="{Binding Title}"/>
<GridViewColumn Header="Status" DisplayMemberBinding="{Binding Status}"/>
This works however I'm not sure if this is the WPF-way of doing things.
However, I'm stuck at the saving process:
Try
Dim myWriter As New IO.StreamWriter(PATH_DATABASE)
For Each wnd As Window In Application.Current.Windows
If wnd.GetType Is GetType(MainWindow) Then
For Each myItem As ListViewItem In DirectCast(wnd, MainWindow).Listview1.Items
myWriter.WriteLine(Song, Artist, Status)
Next
End If
Next
myWriter.Close()
Catch ex As Exception
MsgBox("Error: " & ex.Message, vbCritical, "Error")
End Try
This doesn't work.
Also, PATH_DATABASE is just a directory.
To summarise, could an expert review my code and check if I'm doing things "right" and, if possible, could you help me with the saving and loading process?
Thanks.
I just updated your structure (I used a class, just force of habit) to use automatic property values which are available in recent versions of VS. The default implementation of properties is built in along with the backer fields. It is used just as before.
You are on the right track with ObservableCollection but you you need to implement INotifyPropertyChanged by adding the Event PropertyChanged and providing the Sub OnPropertyChanged to raise the event.
Set up the bindings on the controls and set the ItemsSource ot the ListView to your ObservableList.
To save Import System.Text so you can use a StringBuilder. The loop through the list to build the string to save to a text file.
I didn't spend any time trying to make the XAML Windows look nice. Pardon the ugliness.
Class MainWindow
Public Songs As New ObservableCollection(Of Song)
Protected Sub OnLoad(sender As Object, e As RoutedEventArgs)
Songs.Add(New Song("Nirvana", "Smells Like Teen Spirit", "Open"))
Songs.Add(New Song("John Lennon", "Imagine", "In Stock"))
Songs.Add(New Song("U2", "One", "Unknown"))
Songs.Add(New Song("Michael Jackson", "Billie Jean", "Open"))
lvSongs.ItemsSource = Songs
End Sub
Private Sub BtnAdd_Click(sender As Object, e As RoutedEventArgs)
Dim frm As New frmAdd
frm.mainForm = Me 'send the instance of the current form to the new form
frm.ShowDialog()
End Sub
Private Sub SaveList()
Dim sb As New StringBuilder
For Each item As Song In Songs
sb.AppendLine($"{item.Artist}|{item.Title}|{item.Status}")
Next
'call sb.ToString and write it to a .txt file
End Sub
End Class
MainWindow XAML
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WPF_BindComboBox"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid Loaded="OnLoad"
Name="root">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ListView Margin="10" Name="lvSongs" Grid.Row="0">
<ListView.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text="Name: " />
<TextBox x:Name="txtArtist"
Text="{Binding Artist, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
FontWeight="Bold"/>
<TextBlock Text=", " />
<TextBlock Text="Age: " />
<TextBox Text="{Binding Title}"
FontWeight="Bold" />
<TextBlock Text=" (" />
<TextBox Text="{Binding Status}" />
<TextBlock Text=")" />
</WrapPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button x:Name="btnAdd" Grid.Row="1" Height="25" Width="100" Content="Add a Song" Click="BtnAdd_Click"/>
</Grid>
</Window>
The Song class
Public Class Song
Implements INotifyPropertyChanged
Public Property Artist() As String
Public Property Title() As String
Public Property Status() As String
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Protected Sub OnPropertyChanged(ByVal name As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(name))
End Sub
Public Sub New(art As String, Ttl As String, Stat As String)
Artist = art
Title = Ttl
Status = Stat
End Sub
End Class
The Add Window
Public Class frmAdd
Public mainForm As MainWindow
Private Sub BtnAdd_Click(sender As Object, e As RoutedEventArgs)
mainForm.Songs.Add(New Song(txtArtist.Text, txtTitle.Text, txtStatus.Text))
ClearForm()
End Sub
Private Sub ClearForm()
txtArtist.Clear()
txtTitle.Clear()
txtStatus.Clear()
End Sub
End Class
Add Window XAML
<Window x:Class="frmAdd"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WPF_BindComboBox"
mc:Ignorable="d"
Title="frmAdd" Height="172.641" Width="307.547">
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0" Text="Artist" FontSize="16" FontWeight="Bold"/>
<TextBox x:Name="txtArtist" Grid.Column="1" Grid.Row="0" FontSize="16" Width="150" />
<TextBlock Grid.Column="0" Grid.Row="1" Text="Title" FontSize="16" FontWeight="Bold"/>
<TextBox x:Name="txtTitle" Grid.Column="1" Grid.Row="1" FontSize="16" Width="150"/>
<TextBlock Grid.Column="0" Grid.Row="2" Text="Status" FontSize="16" FontWeight="Bold"/>
<TextBox x:Name="txtStatus" Grid.Column="1" Grid.Row="2" FontSize="16" Width="150"/>
<Button x:Name="btnAdd" Grid.Column="1" Grid.Row="4" Content="Add Song" Click="BtnAdd_Click"/>
</Grid>
</Window>
EDIT
Required Imports
Imports System.Collections.ObjectModel
Imports System.ComponentModel
Imports System.Text
I built a simple WPF UserControl that I want to implement several of in another UserControl while maintaining the data binding. I have a simple Debug1 window that implements (1) a bound textblock (to verify data binding only) & button; (2) a single instance of the NumericUpDownBox usercontrol with binding; and (3) an instance of the parent user control containing an instance of the child user control NumericUpDownBox.
Items (1) and (2) are working. Item (3) does not. I feel like I am missing something in the implementation in the parent user control code? I also get a couple of errors that concern me (see below).
Debug1 Window xaml:
<Window x:Class="Debug1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:CFIT"
mc:Ignorable="d"
Title="Debug1" Height="225" Width="600"
Closing="ClosingDebug1">
<Grid>
<StackPanel Grid.Row="0" Orientation="Horizontal">
<Button Name="NextButton" Content="Next" Click="ClickNext" Width="50" />
<TextBox Text="{Binding Path=Years, Mode=TwoWay}" />
</StackPanel>
<local:NumericUpDownBox Grid.Row="1" x:Name="SingleBox" Label="TBD" Increment="2" Value="{Binding Path=Years}"/>
<local:YMDH_TimeControl Grid.Row="2" x:Name="TimeControl" YearsValue="{Binding Path=Years}" />
</Grid>
</Window>
Debug Window VB Code:
Public Class Debug1
Public Mat As New clsMaterial
Sub New() ' Removed for brevity
End Sub
Private Sub ClickNext(sender As Object, e As RoutedEventArgs)
Mat.Lives(0).MSDateOffset.Years = Mat.Lives(0).MSDateOffset.Years + 5
End Sub
End Class
Parent UserControl xaml:
<UserControl x:Class="YMDH_TimeControl"
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"
xmlns:local="clr-namespace:CFIT"
mc:Ignorable="d"
d:DesignHeight="50" d:DesignWidth="200" MinHeight="30" MinWidth="160"
Name="YMDH_TimeControl">
<Grid DataContext="{Binding ElementName=YMDH_TimeControl}">
<local:NumericUpDownBox Grid.Column="0" x:Name="Years"/>
</Grid>
</UserControl>
Parent UserControl VB Code:
Public Class YMDH_TimeControl
Public Shared ReadOnly YearsValueProperty As DependencyProperty = DependencyProperty.Register("YearsValue", GetType(Integer), GetType(YMDH_TimeControl), New FrameworkPropertyMetadata(0, (FrameworkPropertyMetadataOptions.BindsTwoWayByDefault), Nothing, New CoerceValueCallback(AddressOf OnValueChanged)))
Private Shared Function OnValueChanged(d As DependencyObject, value As Object) As Object
If IsNumeric(value) Then
Return CInt(value)
Else
Return Nothing
End If
End Function
Public Property YearsValue As Integer
Get
Return Years.Value
End Get
Set(value As Integer)
Years.Value = value
End Set
End Property
End Class
Child UserControl xaml:
<UserControl x:Class="NumericUpDownBox"
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"
xmlns:local="clr-namespace:CFIT"
mc:Ignorable="d"
d:DesignHeight="50" d:DesignWidth="50" MinHeight="30" MinWidth="40"
Name="NumericUpDownBox">
<Grid DataContext="{Binding ElementName=NumericUpDownBox}">
<Grid.Resources>
... removed for brevity...
</Grid.Resources>
<!--Box Definition-->
<Border Style="{StaticResource SectionNameBorder}" Grid.Column="0">
<Grid Background="Gray" >
<Viewbox>
<TextBlock Name="LabelTextBlock" Text="{Binding Path=Label, ElementName=NumericUpDownBox}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Viewbox>
<Grid Grid.Row="2" Background="White" >
<Grid Grid.Column="0" HorizontalAlignment="Right">
<Viewbox>
<TextBox Name="ValueTextBox" BorderBrush="White" Text="{Binding Path=Value, Mode=TwoWay, ElementName=NumericUpDownBox, UpdateSourceTrigger=PropertyChanged}" />
</Viewbox>
</Grid>
<Grid Grid.Column="1">
<RepeatButton Name="UpArrow" Style="{StaticResource UpArrowStyle}" Tag="+" Click="TickValues" />
<RepeatButton Name="DownArrow" Style="{StaticResource DownArrowStyle}" Tag="-" Click="TickValues" />
</Grid>
</Grid>
</Grid>
</Border>
</Grid>
</UserControl>
Child UserControl VB Code:
Public Class NumericUpDownBox
Private Const DefaultInc As Integer = 1
Public Sub New()
InitializeComponent()
If Increment = 0 Then Increment = DefaultInc
End Sub
Private Sub TickValues(sender As Object, e As RoutedEventArgs)
Dim Sign As Char
Dim Tag As String
Tag = sender.tag.ToString
If Len(Tag) = 0 Then Exit Sub
Sign = Left(Tag, 1)
If Sign = "+" Then
Value = Value + Increment
ElseIf Sign = "-" Then
Value = Value - Increment
End If
End Sub
Public Property Value As Integer
Get
Return CInt(GetValue(ValueProperty))
End Get
Set(value As Integer)
If value < 0 Then value = 0
SetValue(ValueProperty, value)
End Set
End Property
Public Property Label As String
Get
Return GetValue(LabelProperty)
End Get
Set(value As String)
SetValue(LabelProperty, value)
End Set
End Property
Public Property Increment As Integer
Get
Return CInt(GetValue(IncProperty))
End Get
Set(value As Integer)
If value < 0 Then value = 0
SetValue(IncProperty, value)
End Set
End Property
Public Shared ReadOnly ValueProperty As DependencyProperty = DependencyProperty.Register("Value", GetType(Integer), GetType(NumericUpDownBox), New FrameworkPropertyMetadata(0, (FrameworkPropertyMetadataOptions.BindsTwoWayByDefault), Nothing, New CoerceValueCallback(AddressOf OnValueChanged)))
Public Shared ReadOnly LabelProperty As DependencyProperty = DependencyProperty.Register("Label", GetType(String), GetType(NumericUpDownBox), New FrameworkPropertyMetadata(""))
Public Shared ReadOnly IncProperty As DependencyProperty = DependencyProperty.Register("Increment", GetType(Integer), GetType(NumericUpDownBox), New FrameworkPropertyMetadata(0, Nothing, New CoerceValueCallback(AddressOf OnValueChanged)))
Private Shared Function OnValueChanged(d As DependencyObject, value As Object) As Object
If IsNumeric(value) Then
Return CInt(value)
Else
Return Nothing
End If
End Function
End Class
Example Information Error in the Output Window:
System.Windows.Data Information: 10 : Cannot retrieve value using the binding and no valid fallback value exists; using default instead. BindingExpression:Path=Years; DataItem=null; target element is 'YMDH_TimeControl' (Name='TimeControl'); target property is 'YearsValue' (type 'Int32')
How do I get my data binding to pass through my parent user control?
Im very new to the WPF and MVVM (this is my 1st project in WPF). The project I'm working on is supposed to accept some search criteria, and display the results in the grid. To construct the query I'm using Dynamic LINQ queries. I seem to have issues managing instances of my ProjectSearchViewModel which corresponds to the view that's responsible for collecting the search criteria and executing the query. One instance is created when I create MainWindowViewModel. This creates all other viewmodel instances. This is what I expect. But when time comes to Show the MainWindow, I get another ProjectSearchViewModel, I guess from the binding.
The general idea is this:
The search criteria are filled in the ProjectSearchView.
When Load Command is pressed, I send SearchResultMessage using Reactive Extensions method.
The message is picked up by MainWindowViewModel
MainWindowViewModel is querying the ProjectSearchViewModel.SearchResult and assigning the IObservable List to AllProjectsViewModel.AllProjects which is bound to a datagrid to show the results (AllProjectView is responsible to show the grid with resulting projects list)
The problem is that the parameter filling and sending of the SearchResultMessage happens in one instance of ProjectSearchViewModel and the actual querying of SearchResult from MainWindowViewModel happens in another instance, where all the search criteria are empty.
I guess I have no choice but to post my code: Here it the abridged version of it, omitting some iDisposable plumbing and such. For my model I use Entity Framework 4.
As I mentioned, I'm a total newbie so If anyone sees any blatant disregard for common sense, please set me straight.
Imports Cheminator.ViewModel
Partial Public Class App
Inherits Application
Private viewModel As MainWindowViewModel
Private window As MainWindow
Protected Overrides Sub OnStartup(ByVal e As StartupEventArgs)
MyBase.OnStartup(e)
window = New MainWindow
viewModel = New MainWindowViewModel '1st instance of ProjectSearchViewModel created Here
window.DataContext = viewModel
window.Show() '2nd instance of ProjectSearchViewModel created Here
End Sub
End Class
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Cheminator"
xmlns:vm="clr-namespace:Cheminator.ViewModel"
xmlns:vw="clr-namespace:Cheminator.Views"
xmlns:dx="http://schemas.devexpress.com/winfx/2008/xaml/core"
xmlns:dxd="http://schemas.devexpress.com/winfx/2008/xaml/docking"
xmlns:dxe="http://schemas.devexpress.com/winfx/2008/xaml/editors"
xmlns:dxnb="http://schemas.devexpress.com/winfx/2008/xaml/navbar"
xmlns:dxg="http://schemas.devexpress.com/winfx/2008/xaml/grid"
xmlns:collections="clr-namespace:System.Collections;assembly=mscorlib"
Title="DXWpfApplication" Height="600" Width="800"
dx:ThemeManager.ThemeName="Office2007Blue"
>
<Window.Resources>
<ResourceDictionary Source="MainWindowResources.xaml" />
</Window.Resources>
<dxd:DockLayoutManager>
<dxd:LayoutGroup>
<dxd:LayoutGroup Orientation="Vertical" Width="3*">
<dxd:DocumentGroup Height="3*" SelectedTabIndex="0">
<dxd:DocumentPanel Caption="Document1" Height="3*" >
<ContentControl
Content="{Binding Path=ProjectsVM}"
/>
</dxd:DocumentPanel>
</dxd:DocumentGroup>
<dxd:LayoutPanel Caption="Search Criteria" Height="*" CaptionImage="Images/Icons/DetailView.png">
<ContentControl
Content="{Binding Path=ProjectsSearchVM}"
/>
</dxd:LayoutPanel>
</dxd:LayoutGroup>
</dxd:LayoutGroup>
</dxd:DockLayoutManager>
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Cheminator"
xmlns:vm="clr-namespace:Cheminator.ViewModel"
xmlns:vw="clr-namespace:Cheminator.Views" >
<DataTemplate DataType="{x:Type vm:AllProjectsViewModel}">
<vw:AllProjectsView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:ProjectSearchViewModel}">
<vw:ProjectSearchView />
</DataTemplate>
<UserControl x:Class="Cheminator.Views.AllProjectsView"
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:dx="http://schemas.devexpress.com/winfx/2008/xaml/core"
xmlns:dxd="http://schemas.devexpress.com/winfx/2008/xaml/docking"
xmlns:dxe="http://schemas.devexpress.com/winfx/2008/xaml/editors"
xmlns:dxg="http://schemas.devexpress.com/winfx/2008/xaml/grid"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:vm="clr-namespace:Cheminator.ViewModel"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<dxg:GridControl AutoPopulateColumns="True" ShowBorder="False" >
<dxg:GridControl.DataSource>
<Binding Path="AllProjects"/>
</dxg:GridControl.DataSource>
<dxg:GridControl.View>
<dxg:TableView>
</dxg:TableView>
</dxg:GridControl.View>
</dxg:GridControl>
</UserControl>
<UserControl x:Class="Cheminator.Views.ProjectSearchView"
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:vm="clr-namespace:Cheminator.ViewModel"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="160" d:DesignWidth="470">
<Grid Height="160" Width="470">
<Grid.DataContext>
<vm:ProjectSearchViewModel />
</Grid.DataContext>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="175*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Label Content="Cotation ID:" Height="28" Margin="49,12,32,0" Name="Label1" VerticalAlignment="Top" />
<TextBox Grid.Column="1" Height="23" Margin="0,14,159,0" Name="CotationIDTextBox" VerticalAlignment="Top" Text="{Binding Path=CotationID, UpdateSourceTrigger=LostFocus}"/>
<Label Grid.Row="1" Content="Cotation Name:" Height="28" Margin="49,6,6,0" Name="Label2" VerticalAlignment="Top" />
<TextBox Grid.Row="1" Grid.Column="1" Height="23" Margin="0,8,159,0" Name="CotationNameTextBox" VerticalAlignment="Top" Text="{Binding Path=ProjectSummary, UpdateSourceTrigger=PropertyChanged}"/>
<Label Grid.Row="2" Content="User:" Height="28" Margin="49,6,32,0" Name="Label3" VerticalAlignment="Top" />
<TextBox Grid.Row="2" Grid.Column="1" Height="23" Margin="0,8,159,0" Name="UserTextBox" VerticalAlignment="Top" Text="{Binding Path=UserName, UpdateSourceTrigger=PropertyChanged}"/>
<Button
Command="{Binding Path=LoadCommand}"
Content="_Load"
HorizontalAlignment="Right"
Margin="0,10,51,12"
MinWidth="60" Grid.Row="3" Width="72" Grid.Column="1" />
</Grid>
</UserControl>
Public Class MainWindowViewModel
Inherits ViewModelBase
Private _commands As ReadOnlyCollection(Of CommandViewModel)
Private _ProjectsVM As AllProjectsViewModel
Private _ProjectsSearchVM As ProjectSearchViewModel
Public Sub New()
MyBase.DisplayName = "Cheminator"
_ProjectsSearchVM = New ProjectSearchViewModel
Messenger.[Default].OfType(Of SearchResultMessage) _
.Subscribe(Sub(param As SearchResultMessage)
ProjectsVM.AllProjects = ProjectsSearchVM.SearchResult
End Sub)
_ProjectsVM = New AllProjectsViewModel
End Sub
Public ReadOnly Property ProjectsVM As AllProjectsViewModel
Get
If (_ProjectsVM IsNot Nothing) Then
Return _ProjectsVM
End If
Return Nothing
End Get
End Property
Public ReadOnly Property ProjectsSearchVM As ProjectSearchViewModel
Get
If (_ProjectsSearchVM IsNot Nothing) Then
Return _ProjectsSearchVM
End If
Return Nothing
End Get
End Property
End Class
Public Class AllProjectsViewModel
Inherits ViewModelBase
Private m_ProjectsList As ObservableCollection(Of xGMV_Cotation)
Public Sub New()
MyBase.DisplayName = "Temp AllProjectsViewModel Name" 'Strings.AllProjectsViewModel_DisplayName
End Sub
Public Property AllProjects() As ObservableCollection(Of xGMV_Cotation)
Get
Return m_ProjectsList
End Get
Set(ByVal value As ObservableCollection(Of xGMV_Cotation))
m_ProjectsList = value
OnPropertyChanged("AllProjects")
End Set
End Property
End Class
Public Class ProjectSearchViewModel
Inherits ViewModelBase
Private m_ProjectsList As ObservableCollection(Of xGMV_Cotation)
Public Sub New()
MyBase.DisplayName = "Cheminator.ProjectSearchViewModel"
End Sub
Dim _CotationID As Integer
Public Property CotationID As Integer
Get
Return _CotationID
End Get
Set(ByVal value As Integer)
_CotationID = value
MyBase.OnPropertyChanged("CotationID")
End Set
End Property
Public Property ProjectSummary As String
Public Property UserName As String
Private m_LoadCommand As RelayCommand
Public ReadOnly Property LoadCommand As ICommand
Get
If m_LoadCommand Is Nothing Then
Dim LoadAction As New Action(Of Object)(AddressOf Me.Load)
m_LoadCommand = New RelayCommand(LoadAction)
End If
Return m_LoadCommand
End Get
End Property
Public ReadOnly Property SearchResult() As ObservableCollection(Of xGMV_Cotation)
Get
Dim xWhere As String = ""
Dim i As Integer = 0
Dim parameterList As New ArrayList
If Not String.IsNullOrEmpty(CotationID) Then
xWhere = String.Format("CotationID = #{0}", i)
parameterList.Add(CotationID)
i += 1
End If
If Not String.IsNullOrEmpty(ProjectSummary) Then
If i > 0 Then
xWhere = xWhere & " AND "
End If
xWhere = xWhere & String.Format("ProjectSummary = '#{0}'", i)
i += 1
parameterList.Add(ProjectSummary)
End If
If Not String.IsNullOrEmpty(UserName) Then
If i > 0 Then
xWhere = xWhere & " AND "
End If
xWhere = xWhere & String.Format("UserName = '#{0}'", i)
i += 1
parameterList.Add(UserName)
End If
Return New ObservableCollection(Of xGMV_Cotation)(DataContext.DBEntities.xGMV_Cotations.Where(xWhere, parameterList.ToArray))
End Get
End Property
Private Sub Load()
OnPropertyChanged("SearchResult")
Messenger.Default.Send(New SearchResultMessage())
End Sub
End Class
everytime you create a ProjectSearchView usercontrol, you also create a ProjectSearchViewModel too. you should remove the following from your usercontrl xaml.
<Grid.DataContext>
<vm:ProjectSearchViewModel />
</Grid.DataContext>
because of your datatemplate you do not need to set the datacontext for ProjectSearchView, it already has the right datacontext.
Silverlight's dataInput:Label and dataInput:DescriptionViewer controls provide a useful function through their ability to bind to a TextBox. Rather than hard-coding the text content of my labels and descriptions with System.ComponentModel.DataAnnotations.DisplayAttribute, I'd prefer to bind these attributes to properties of a class that holds admin-editable text. This works for Label, but not for DescriptionViewer.
Specifically, this Label works:
<dataInput:Label Grid.Row="00" Grid.Column="0"
Target="{Binding ElementName=inpStatus}"
Content="{Binding StatusLabel}" IsRequired="true" />
but this DescriptionViewer is invisible:
<dataInput:DescriptionViewer Grid.Row="00" Grid.Column="3"
Target="{Binding ElementName=inpStatus}"
Name="dvBound2" Description="{Binding Path=StatusDescription}" />
When I remove the binding to my TextBox, the DescriptionViewer is shown:
<dataInput:DescriptionViewer Grid.Row="00" Grid.Column="2"
Name="dvBound1" Description="{Binding Path=StatusDescription}" />
Should it be possible to bind a DescriptionViewer to both a TextBox and an alternate Description source?
Thanks in advance!
MainPage.xaml:
<UserControl x:Class="DataInputDemo.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controlsToolkit="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Toolkit"
xmlns:dataInput="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.Input"
xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
<Grid x:Name="LayoutRoot">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="142"></ColumnDefinition>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="24"></ColumnDefinition>
<ColumnDefinition Width="24"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="30"></RowDefinition>
<RowDefinition Height="30"></RowDefinition>
</Grid.RowDefinitions>
<dataInput:Label Grid.Row="00" Grid.Column="0"
Target="{Binding ElementName=inpStatus}"
Content="{Binding StatusLabel}" IsRequired="true" />
<TextBox Grid.Row="00" Grid.Column="1"
Text="{Binding Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnExceptions=True, Path=Status}"
Name="inpStatus" MaxLength="025"
BindingValidationError="_BindingValidationError" />
<dataInput:DescriptionViewer Grid.Row="00" Grid.Column="2"
Name="dvBound1" Description="{Binding Path=StatusDescription}" />
<!-- Following DescriptionViewer is not shown. -->
<dataInput:DescriptionViewer Grid.Row="00" Grid.Column="3"
Target="{Binding ElementName=inpStatus}"
Name="dvBound2" Description="{Binding Path=StatusDescription}" />
<dataInput:ValidationSummary Grid.Row="01" Grid.Column="0"
Grid.ColumnSpan="4" Name="VS1" />
</Grid>
</UserControl>
MainPage.xaml.vb:
Partial Public Class MainPage
Inherits UserControl
Public myDataClass = New DataClass
Public Sub New()
InitializeComponent()
myDataClass.status = "Default Status"
myDataClass.StatusLabel = "Status Label"
myDataClass.StatusDescription = "Status Description"
LayoutRoot.DataContext = myDataClass
End Sub
Private Sub _BindingValidationError(ByVal sender As Object, ByVal e As ValidationErrorEventArgs)
End Sub
End Class
DataClass.vb:
Imports System.Collections.ObjectModel
Imports System.ComponentModel
Imports System.Runtime.Serialization
Imports System.ComponentModel.DataAnnotations
Imports System.Windows.Controls
Public Class DataClass
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
Function ValidateEntry(ByVal fieldname As String, ByRef value As Object) As Object
Dim ctx As New ValidationContext(Me, Nothing, Nothing)
ctx.MemberName = fieldname
Validator.ValidateProperty(value, ctx)
Return value
End Function
<DataMember()> _
<Required()> _
<StringLength(100)> _
Public Property Status() As String
Get
Return _Status
End Get
Set(ByVal value As String)
_Status = ValidateEntry("Status", value)
NotifyPropertyChanged("")
End Set
End Property
Private _Status As String
<DataMember()> _
Public Property StatusLabel() As String
Get
Return _StatusLabel
End Get
Set(ByVal value As String)
_StatusLabel = value
NotifyPropertyChanged("")
End Set
End Property
Private _StatusLabel As String
<DataMember()> _
Public Property StatusDescription() As String
Get
Return _StatusDescription
End Get
Set(ByVal value As String)
_StatusDescription = value
NotifyPropertyChanged("")
End Set
End Property
Private _StatusDescription As String
End Class
I had the exact same problem with the DescriptionViewer using this markup:
<TextBox x:Name="UserNameTextBox" Text="{Binding UserName, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true, UpdateSourceTrigger=Explicit}" Grid.Column="1" Grid.Row="1"></TextBox>
<dataInput:DescriptionViewer Target="{Binding ElementName=UserNameTextBox}" Description="{Binding Path=CreateUserResources.UserNameDescription, Source={StaticResource GlobalResources}}" Grid.Column="2" Grid.Row="1"></dataInput:DescriptionViewer>
Before binding the Description to its resource it was a plain string in the markup and it did work.
To resolve the problem I have set the PropertyPath of the DescriptionViewer with the property name where the binding is located on the Target control. It is now working correctly.
<TextBox x:Name="UserNameTextBox" Text="{Binding UserName, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true, UpdateSourceTrigger=Explicit}" Grid.Column="1" Grid.Row="1"></TextBox>
<dataInput:DescriptionViewer Target="{Binding ElementName=UserNameTextBox}" PropertyPath="Text" Description="{Binding Path=CreateUserResources.UserNameDescription, Source={StaticResource GlobalResources}}" Grid.Column="2" Grid.Row="1"></dataInput:DescriptionViewer>
If you are using MVVM make sure the PropertyPath does not match a property of your presenter model. I did not investigate but I had an instance where I was forced to change the property name of my presenter to make it work. Seems like the control was trying to parse the metadata from the property of my presenter instead of the target control.
Hope this help.