Can I use VB.Net's generics to simply Wpf notifiable bound controls?
Here's how to use VB.Net's Generics to create notifiable bound controls.
Class MainWindow
Public Sub New()
InitializeComponent()
Me.DataContext = Me
End Sub
Public Property NoNotify As String = "No notify"
Public Property DoesNotify As New NotifiableProperty(Of String)("DoesNotify")
Private Sub TestBtn_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles TestBtn.Click
NoNotify = "Won't Show"
DoesNotify.Value = "Notified!"
End Sub
End Class
Public Class NotifiableProperty(Of T)
Implements System.ComponentModel.INotifyPropertyChanged
Sub New()
End Sub
Sub New(ByVal InitialValue As T)
value = InitialValue
End Sub
Private m_Value As T
Public Property Value As T
Get
Return m_Value
End Get
Set(ByVal value As T)
m_Value = value
RaiseEvent PropertyChanged(Me, New System.ComponentModel.PropertyChangedEventArgs("Value"))
End Set
End Property
Private Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
End Class
<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>
<StackPanel>
<Button Name="TestBtn" Content="Click to Test" Width="72" />
<TextBox Name="NoNotifyTB" Text="{Binding NoNotify}" />
<TextBox Name="DoesNotifyTB" Text="{Binding DoesNotify.Value}"/>
</StackPanel>
</Grid>
</Window>
Related
xaml
<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 x:Name="myGrid">
<Button x:Name="myButton" Height="20" Width="200" VerticalAlignment="Bottom" Content="Unregister The Name Name"/>
</Grid>
</Window>
vb.net
Class MainWindow
Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
Dim myTextBlock As New TextBlock
With myTextBlock
.Height = 30
.Width = 100
.Background = New SolidColorBrush(Colors.Blue)
End With
Me.RegisterName(name:="myTextBlock", scopedElement:=myTextBlock)
myGrid.Children.Add(myTextBlock)
End Sub
Private Sub myButton_Click(sender As Object, e As RoutedEventArgs) Handles myButton.Click
If Reflection.Assembly.GetExecutingAssembly.GetManifestResourceNames.Contains("myTextBlock") Then
MessageBox.Show("Done")
Me.UnregisterName(name:="myTextBlock")
End If
End Sub
End Class
MessageBox doesnt open when I run this project and click myButton.
So, can you advice alternative code for following code?
Reflection.Assembly.GetExecutingAssembly.GetManifestResourceNames.Contains("myTextBlock")
You can use this
Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
Dim myTextBlock As New TextBlock
With myTextBlock
.Height = 30
.Width = 100
.Background = New SolidColorBrush(Colors.Blue)
End With
Me.RegisterName(name:="myTextBlock", scopedElement:=myTextBlock)
myGrid.RegisterName("myTextBlock", myTextBlock)
myGrid.Children.Add(myTextBlock)
End Sub
Private Sub myButton_Click(sender As Object, e As RoutedEventArgs) Handles myButton.Click
Dim btn As Object = Nothing
btn = myGrid.Findname("myTextBlock")
If (Not btn = Nothing) Then
MessageBox.Show("Done")
Me.UnregisterName(name:="myTextBlock")
'myGrid.UnregisterName("myTextBlock")
End If
End Sub
If I run my project, my tooltip converter is run once - I need it to run each time the mouse hoover over a row.
Here's my 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:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:MyTooltipConverter x:Key="MyTooltipConverter" />
</Window.Resources>
<Grid>
<DataGrid x:Name="dataGrid" ItemsSource="{Binding}" HorizontalAlignment="Left" VerticalAlignment="Top" Height="263" Width="507">
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="ToolTip">
<Setter.Value>
<ToolTip Content="{Binding ??, Converter={StaticResource MyTooltipConverter}}" />
</Setter.Value>
</Setter>
</Style>
</DataGrid.Resources>
</DataGrid>
</Grid>
</Window>
And code...
Imports System.Globalization
Class MainWindow
Public Class Person
Public Property Name As String
End Class
Public Persons As New List(Of Person)
Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
For i As Integer = 0 To 5
Persons.Add(New Person With {.Name = "Test " + i.ToString})
Next
dataGrid.DataContext = Persons
End Sub
End Class
Public Class MyTooltipConverter
Implements IValueConverter
Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert
If value Is Nothing Then
Return Nothing
End If
Dim panel As New StackPanel()
panel.Orientation = Orientation.Vertical
Dim block As New TextBlock()
block.Text = Now.ToString
panel.Children.Add(block)
Dim tip As New ToolTip()
tip.Content = panel
Return tip
End Function
Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack
Throw New NotImplementedException()
End Function
End Class
How can I call MyTooltipConveter af get a Tooltip with the current time?
Thanks
Change your MyTooltipConverter.Convert method to Return panel. Returning tip throws a System.InvalidOperationException - 'ToolTip' cannot have a logical or visual parent.
As for the binding, just using the converter works.
<ToolTip Content="{Binding Converter={StaticResource MyTooltipConverter}}" />
Of course, for the time to update you have to move the mouse around to generate a new tooltip. If what you wanted was to have a tooltip that constantly updates then you need to add a timer and update the block.Text.
Something like this:
Public Class MyTooltipConverter
Implements IValueConverter
Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert
If value Is Nothing Then
Return Nothing
End If
Dim panel = New StackPanel()
panel.Orientation = Orientation.Vertical
Dim block As New TextBlock()
block.Text = Now.ToString
panel.Children.Add(block)
Dim timer As New System.Windows.Threading.DispatcherTimer()
timer.Interval = New TimeSpan(0, 0, 1)
AddHandler timer.Tick, Sub()
block.Text = Now.ToString
End Sub
timer.Start()
Debug.WriteLine("Timer Started")
AddHandler panel.Unloaded, Sub(s, e)
timer.Stop()
timer = Nothing
panel = Nothing
Debug.WriteLine("Timer Stopped")
End Sub
Return panel
End Function
Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack
Throw New NotImplementedException()
End Function
End Class
I have a texbox in binding with a property.
<TextBox Name="txtPrice" Grid.Row="0" Grid.Column="2" MaxLength="8" TabIndex="1"
Text="{Binding Price, UpdateSourceTrigger=PropertyChanged, ValidatesOnExceptions=True,
StringFormat= '\{0:#,###.##\}', ConverterCulture=fr-FR}" TextWrapping="Wrap"/>
Private Property _Price As Double
Public Property Price As Double
Get
Return Price
End Get
Set(value As Double)
_Price = Double.Parse(value)
OnPropertyChanged("Price")
End Set
End Property
When I type some chars or the textbox is empty, the button Cmd_Insert must not be enabled, but doesn't work.
Why ? (see Function CanCmd_Insert())
Public ReadOnly Property Cmd_Insert As ICommand
Get
If _Cm_Insert Is Nothing Then
_Cm_Insert = New RelayCommand(AddressOf Cmd_InsertExe, AddressOf CanCmd_Insert)
End If
Return _Cm_Insert
End Get
End Property
Private Sub Cmd_InsertExe()
UPDATE_Price()
End Sub
Private Function CanCmd_Insert() As Boolean
If IsNumeric(Price) = False Then
Return False
Else
Return True
End If
End Function
I added TargetNullValue='' and changed your property to nullable. Refer the below code.
<StackPanel>
<TextBox Name="txtPrice" Grid.Row="0" Grid.Column="2" MaxLength="8" TabIndex="1"
Text="{Binding Price, UpdateSourceTrigger=PropertyChanged, TargetNullValue='',
StringFormat= '\{0:#,###.##\}'}" TextWrapping="Wrap" />
<Button Content="Update" Command="{Binding Cmd_Insert }"></Button>
</StackPanel>
Imports GalaSoft.MvvmLight.CommandWpf
Imports System.ComponentModel
Public Class ViewModel
Implements INotifyPropertyChanged
Private Property _Price As Double?
Public Property Price As Double?
Get
Return _Price
End Get
Set(value As Double?)
_Price = value
OnPropertyChanged("Price")
End Set
End Property
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Private Sub OnPropertyChanged(ByVal info As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(info))
End Sub
Private Property _Cm_Insert As ICommand
Public ReadOnly Property Cmd_Insert As ICommand
Get
If _Cm_Insert Is Nothing Then
_Cm_Insert = New RelayCommand(AddressOf Cmd_InsertExe, AddressOf CanCmd_Insert)
End If
Return _Cm_Insert
End Get
End Property
Private Sub Cmd_InsertExe()
End Sub
Private Function CanCmd_Insert() As Boolean
If IsNumeric(Price) = False Then
Return False
Else
Return True
End If
End Function
End Class
WPF/VB.net newbie here.
I'm trying to filter rows in a datagrid and having a fun time.
I've managed to create a list of objects and use the itemsource property to get the datagrid to populate.
Now I have a checkbox that for arguments-sake I want to click and filter down only those rows that match this criterion.
With the code below I'm getting the general "Object reference not set to an instance of an object." error but a bit lost. I'm sure a VB pro will see it.
I'd prefer to do more in code, rather than XAML if possible.
This is my XAML:
<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>
<DataGrid x:Name="displayGrid" HorizontalAlignment="Left" Margin="62,94,0,0" VerticalAlignment="Top" Height="142" Width="360" SelectionChanged="DataGrid_SelectionChanged"/>
<Button Content="Load" HorizontalAlignment="Left" Margin="62,51,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
<CheckBox x:Name="showOnlyChildren" Content="Show Only Children" HorizontalAlignment="Left" Margin="172,51,0,0" VerticalAlignment="Top" Width="147"/>
</Grid>
</Window>
And this is my code:
Class MainWindow
Class person
Property name
Property age
End Class
Dim listOfPersons As New List(Of person)
Private filteredList As CollectionViewSource
Private Sub DataGrid_SelectionChanged(sender As Object, e As SelectionChangedEventArgs)
End Sub
Private Sub Button_Click(sender As Object, e As RoutedEventArgs)
Dim aPerson As New person With {
.name = "Fred Smith",
.age = 12}
listOfPersons.Add(aPerson)
Dim bPerson As New person With {
.name = "Tom Jones",
.age = 50}
listOfPersons.Add(bPerson)
displayGrid.ItemsSource = CollectionViewSource.GetDefaultView(listOfPersons)
End Sub
Private Sub ShowOnlyChildrenFilter(ByVal sender As Object, ByVal e As FilterEventArgs)
Dim person As person = TryCast(e.Item, person)
If person IsNot Nothing Then
' Filter out persons with age less than 18
If person.age < 19 Then
e.Accepted = True
Else
e.Accepted = False
End If
End If
End Sub
Private Sub AddFiltering(ByVal sender As Object, ByVal args As RoutedEventArgs) Handles showOnlyChildren.Checked
AddHandler filteredList.Filter, AddressOf ShowOnlyChildrenFilter
End Sub
Private Sub RemoveFiltering(ByVal sender As Object, ByVal args As RoutedEventArgs)
RemoveHandler filteredList.Filter, AddressOf ShowOnlyChildrenFilter
End Sub
End Class
EDIT: OK slowly but surely getting there. I'm incorporated some changes that I found here and thanks to help I got here.... This is what the code looks like now:
Imports System.ComponentModel
Class MainWindow
Class person
Property name
Property age
End Class
Dim listOfPersons As New List(Of person)
Private filteredList As CollectionViewSource
Dim view As ICollectionView
Private Sub DataGrid_SelectionChanged(sender As Object, e As SelectionChangedEventArgs)
End Sub
Private Sub Button_Click(sender As Object, e As RoutedEventArgs)
Dim aPerson As New person With {
.name = "Fred Smith",
.age = 12}
listOfPersons.Add(aPerson)
Dim bPerson As New person With {
.name = "Tom Jones",
.age = 50}
listOfPersons.Add(bPerson)
view = CollectionViewSource.GetDefaultView(listOfPersons)
displayGrid.ItemsSource = view
End Sub
Function ShowOnlyChildrenFilter(ByVal param As Object) As Boolean
Dim person As person = TryCast(param, person)
Dim retValue As Boolean
If person IsNot Nothing Then
' Filter out persons with age less than 18
If person.age < 19 Then
retValue = True
Else
retValue = False
End If
End If
Return retValue
End Function
Private Sub showOnlyChildren_Checked(sender As Object, e As RoutedEventArgs) Handles showOnlyChildren.Checked
If showOnlyChildren.IsChecked = True Then
view.Filter = New Predicate(Of Object)(AddressOf ShowOnlyChildrenFilter)
Else
'what goes here?
End If
End Sub
End Class
The only thing I'm missing is how to refresh the datagrid, when the checkbox is unchecked. Thanks to all. I'm still amazed at how complex and mindbending this is for what I would have thought would be quite simple.
This should be a comment and not an answer but I am unable to comment as not enough rep!
Have you tried using
ICollectionView
I have a C# example I can provide if you are able to convert it!
EDIT:
I thought I would just chuck in the example as it may help a little
private void cbBlahYear_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
lvwMainBlahFilter();
}
private void lvwMainBlahFilter()
{
ICollectionView view = CollectionViewSource.GetDefaultView(lvwMainBlah.ItemsSource);
view.Filter = null;
view.Filter = new Predicate<object>(FilterBlahByYearID);
view.SortDescriptions.Add(new SortDescription("Forename", ListSortDirection.Ascending));
}
private Boolean FilterBlahByYearID(object obj)
{
BlahModel item = obj as BlahModel;
if (item == null) return false;
Int32 myID = 0;
if (cbBlahYear.SelectedItem != null)
{
YearModel year = cbBlahYear.SelectedItem as YearModel;
myID = year.id;
}
if (myID == 0) return false;
if (item.YearID == myID) return true;
return false;
}
The variable filteredList that you are tring to:
AddHandler filteredList.Filter, AddressOf ShowOnlyChildrenFilter
Is still nothing at this line. Try declaring it as NEW first:
Private filteredList As New CollectionV
OK It is done. Made a couple more newbie errors on the above code. So here is the working version. Tick a checkbox and it filters on a condition.
VB code:
Imports System.ComponentModel
Class MainWindow
Class person
Property name
Property age
End Class
Dim listOfPersons As New List(Of person)
Dim view As ICollectionView
Private Sub DataGrid_SelectionChanged(sender As Object, e As SelectionChangedEventArgs)
MsgBox("changed")
End Sub
Private Sub Button_Click(sender As Object, e As RoutedEventArgs)
Dim aPerson As New person With {
.name = "Fred Smith",
.age = 12}
listOfPersons.Add(aPerson)
Dim bPerson As New person With {
.name = "Tom Jones",
.age = 50}
listOfPersons.Add(bPerson)
view = CollectionViewSource.GetDefaultView(listOfPersons)
displayGrid.ItemsSource = view
End Sub
Function ShowOnlyChildrenFilter(ByVal param As Object) As Boolean
Dim person As person = TryCast(param, person)
Dim retValue As Boolean
If person IsNot Nothing Then
' Filter out persons with age less than 18
If person.age < 19 Then
retValue = True
Else
retValue = False
End If
End If
Return retValue
End Function
Private Sub showOnlyChildren_Checked(sender As Object, e As RoutedEventArgs) Handles showOnlyChildren.Checked
If showOnlyChildren.IsChecked = True Then
view.Filter = New Predicate(Of Object)(AddressOf ShowOnlyChildrenFilter)
End If
End Sub
Private Sub showOnlyChildren_UnChecked(sender As Object, e As RoutedEventArgs) Handles showOnlyChildren.Unchecked
If showOnlyChildren.IsChecked = False Then
view.Filter = Nothing
End If
End Sub
End Class
With XAML:
<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>
<DataGrid x:Name="displayGrid" HorizontalAlignment="Left" Margin="62,94,0,0" VerticalAlignment="Top" Height="142" Width="360" SelectionChanged="DataGrid_SelectionChanged"/>
<Button Content="Load" HorizontalAlignment="Left" Margin="62,51,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
<CheckBox x:Name="showOnlyChildren" Content="Show Only Children" HorizontalAlignment="Left" Margin="172,51,0,0" VerticalAlignment="Top" Width="147"/>
</Grid>
</Window>
I am trying to do a binding as with following data formats:
Public Structure ItemBase
Property ID As String
Property Description As String
End Structure
Namespace Classes
Public Class StockEntityClass
Implements INotifyPropertyChanged
#Region "Property Variables"
Property ID As String
Property Namee As String
Property Units As String
Property ContactID As String
Property SetCount As Integer
Property VatOnMargin As Boolean
Property Vat As Double
Property Code As String
Property _ContactNamee As String
#End Region
Public Sub New()
_IDValue = Now.ToString
_NameeValue = ""
_UnitsValue = "Pcs"
_ContactIDValue = ""
_SetCountValue = 0
_VatOnMarginValue = False
_VatValue = 14.5
_CodeValue = ""
_ContactNamee = ""
End Sub
End Class
End Namespace
In my wpf xaml window which is DataContext to Stock Entity variable, I have a combo box that is bound to BindingList(of ItemBase) and the user will select an Item and that ItemID as to get assosiated to StockEntity.ContactID.
Following is the xaml code:
<ComboBox Name="VendorsComboBox" Grid.Row="2" Grid.Column="1" VerticalAlignment="Center" DisplayMemberPath="Description" SelectedItem="{Binding Path=CustomerID}"/>
Following is Binding Code:
Dim Stock As Classes.StockEntityClass
VendorsComboBox.ItemsSource = Contacts.DBAccessFunctions.Get_ContactsByType_BaseList(DataSource, "Vendor")
StockEntityStack.DataContext = Stock
Where StockEntityStack contains the UI part which is datacontext bind to stock variable.
Could you please tell me how to write the xaml databound to it.
Try something like that:
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"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<ComboBox ItemsSource="{Binding MySource}"
SelectedValue="{Binding ContactID}"
DisplayMemberPath="Description"
SelectedValuePath="ID"
Width="180" Height="25"
/>
<StackPanel Orientation="Horizontal" Margin="10" >
<TextBlock Text="Selected ID:" />
<TextBlock Text="{Binding ContactID}" />
</StackPanel>
</StackPanel>
</Window>
MainWindow (code-behind file):
Class MainWindow
Public Sub New()
InitializeComponent()
Me.DataContext = New MainViewModel()
End Sub
End Class
MainViewModel file:
Imports System.ComponentModel
Public Class MainViewModel
Implements INotifyPropertyChanged
#Region "Fields"
Private _selectedContactID As String = String.Empty
#End Region
#Region "Property Variables"
Property MySource As List(Of ItemBase) = New List(Of ItemBase)
Public Property ContactID As String
Get
Return _selectedContactID
End Get
Set(ByVal value As String)
_selectedContactID = value
OnPropertyChanged("ContactID")
End Set
End Property
#End Region
Public Sub New()
MySource.Add(New ItemBase(1, "test1"))
MySource.Add(New ItemBase(2, "test2"))
MySource.Add(New ItemBase(3, "test3"))
MySource.Add(New ItemBase(4, "test4"))
MySource.Add(New ItemBase(5, "test5"))
End Sub
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Protected Sub OnPropertyChanged(ByVal propertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
End Class
Public Structure ItemBase
Public Sub New( _
ByVal _id As String,
ByVal _description As String
)
ID = _id
Description = _description
End Sub
Property ID As String
Property Description As String
End Structure