I have a ListView which should contain per line an image and text. The binding of the text works fine, but the image is not be shown.
My XAML-Markup:
<ListView Name="lvUpgrade">
<ListView.View>
<GridView>
<GridViewColumn Width="20px">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Image Source="{Binding Path=Icon}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="75px" DisplayMemberBinding="{Binding Path=Time, StringFormat={}{0:HH:mm:ss}}" />
<GridViewColumn Width="300px" Header="Nachricht" DisplayMemberBinding="{Binding Path=Message}" />
</GridView>
</ListView.View>
</ListView>
My code behind:
Public Class Upgrade
Public Sub AddMessage(ByVal message As Message)
Me.lvUpgrade.Items.Add(message)
End Sub
Public Class Message
Public Enum MessageType
Normal
Information
Warning
[Error]
End Enum
Public Sub New(ByVal type As MessageType, ByVal message As String)
_Type = type
_Message = message
End Sub
Private _Type As MessageType = MessageType.Normal
Public ReadOnly Property Type As MessageType
Get
Return _Type
End Get
End Property
Private _Message As String = String.Empty
Public ReadOnly Property Message As String
Get
Return _Message
End Get
End Property
Private _Time As DateTime = Now
Public ReadOnly Property Time As DateTime
Get
Return _Time
End Get
End Property
Public ReadOnly Property Icon As System.Drawing.Image
Get
Select Case Me.Type
Case MessageType.Information
Return My.Resources.Information16
Case MessageType.Warning
Return My.Resources.Alert16
Case MessageType.Error
Return My.Resources.Error16
Case Else
End Select
Return Nothing
End Get
End Property
End Class
End Class
To bind the image I use a property which returns the image from the local resources. I use the same way to bind the image to the control like I do it for the text. Is this try wrong or why does it not works?
Thanks for any response.
You have to Convert the Image to a BitmapImage:
For this you could define the following Extension in a separate Module.
This offers the opportunity to use the Function on every Image you will have in your code:
Module Extensions
<Extension()>
Public Function ToBitmapImage(ByVal lBitmap As Bitmap) As BitmapImage
Dim lMemoryStream As New MemoryStream()
Dim lBitmapImage As New BitmapImage()
lBitmap.Save(lMemoryStream, ImageFormat.Png)
lBitmapImage.BeginInit()
lBitmapImage.StreamSource = New MemoryStream(lMemoryStream.ToArray())
lBitmapImage.EndInit()
Return lBitmapImage
End Function
End Module
After that you have to edit your property like:
Public ReadOnly Property Icon As BitmapImage
Get
Select Case Me.Type
Case MessageType.Information
Return My.Resources.Information16.ToBitmapImage
Case MessageType.Warning
Return My.Resources.Alert16.ToBitmapImage
Case MessageType.Error
Return My.Resources.Error16.ToBitmapImage
Case Else
End Select
Return Nothing
End Get
End Property
Related
I am trying to get a label to show specific text while also being bound to a variable in the VB.Net code. I can make a binding but I cant get it to add the static text.
What I have so far:
<Label x:Name="TestLabel" Content="{Binding Path=Row, StringFormat='Row #{0}'}"
HorizontalAlignment="Left"
Height="35"
Margin="203,21,0,0"
VerticalAlignment="Top"
Width="83"
FontSize="18">
with
Public Class Row
Implements INotifyPropertyChanged
Private _Row As Byte
Public Property Row() As Byte
Get
Return _Row
End Get
Set(ByVal value As Byte)
_Row = value
OnPropertyChanged(New PropertyChangedEventArgs("Row"))
End Set
End Property
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Public Sub OnPropertyChanged(ByVal e As PropertyChangedEventArgs)
If Not PropertyChangedEvent Is Nothing Then
RaiseEvent PropertyChanged(Me, e)
End If
End Sub
End Class
and
Private Rows As New Row
Public Sub New()
InitializeComponent()
TestLabel.DataContext = Rows
Rows.Row = MyTextBox.Text.HandledStringtoSByte
End Sub
The extension code (since I have a custom extension):
''' <summary>
''' Handles conversion of string variable to Tiny Integer
''' </summary>
''' <param name="s"></param>
''' <param name="I">Returned if conversion fails.</param>
''' <returns>Signed 8bit Integer</returns>
''' <remarks></remarks>
<Runtime.CompilerServices.Extension()> _
Public Function HandledStringtoSByte(ByRef S As String, Optional I As SByte = 0) As SByte
Try
If S = String.Empty Then
Return I
Else
Return SByte.Parse(S)
End If
Catch
Dim result As String = String.Empty
Dim ReturnByte As SByte
Dim Parsed As Byte
For Each Character In S.ToCharArray
If Character = "-" Then
If S.Substring(0, 1).ToString <> "-" Then
Exit For
End If
End If
If Character = "." Then
Exit For
End If
If Byte.TryParse(Character, Parsed) Then
result = result + Parsed.ToString
End If
Next
If result <> String.Empty Then
If SByte.TryParse(result, ReturnByte) Then
Return SByte.Parse(ReturnByte)
Else
If Short.Parse(result) > Short.Parse(SByte.MaxValue.ToString) Then
Return SByte.MaxValue
ElseIf Short.Parse(result) < Short.Parse(SByte.MinValue.ToString) Then
Return SByte.MinValue
Else
Return SByte.Parse(ReturnByte)
End If
End If
Else
Return I
End If
End Try
End Function
Now I thought that using the stringformat in binding would add the static text and place the bound variable into the {0} spot but all is gives me is the bound variable in the label.
What am i doing wrong?
Binding target is Content property which is Object type, that is why you cannot use StringFormat with binding.
Instead use ContentStringFormat property
<Label Content="{Binding Path=Row}"
ContentStringFormat="Row #{0}" />
Another approach: create readonly property in the ViewModel which will represent value in wanted format
Private _Row As Byte
Public Property Row() As Byte
Get
Return _Row
End Get
Set(ByVal value As Byte)
_Row = value
OnPropertyChanged(New PropertyChangedEventArgs("Row"))
OnPropertyChanged(New PropertyChangedEventArgs("RowText"))
End Set
End Property
Public ReadOnly Property RowText As String
Get
Return String.Format("Row #{0}", Me.Row)
End Get
End Property
Then bind this property to the View
<Label Content="{Binding Path=RowText}"/>
The problem is that Binding.StringFormat is "a string that specifies how to format the binding if it displays the bound value as a string". In practice it seems to work only if the target property is of type string - as you pointed out it's working for TextBlock.Text (which is of type string) and not for Label.Content (which is of type object). There are several ways to approach this problem, one of them would be to nest a TextBlock in the Content property:
<Label>
<TextBlock Text="{Binding Path=Row, StringFormat='Row #{0}'}" />
</Label>
This doesn't really introduce any additional complexity to the visual tree since strings are by default presented by TextBlocks.
Otherwise you could create your own converter, or you could go with Fabio's solution and utilize Label.ContentStringFormat property.
Here's a way to bind to multiple properties :
a MultiBinding
an IMultiValueConverter
Code:
Imports System.Globalization
Imports System.Text
Class MainWindow
Public Shared ReadOnly Text1Property As DependencyProperty = DependencyProperty.Register(
"Text1", GetType(String), GetType(MainWindow), New PropertyMetadata(Nothing))
Public Property Text1 As String
Get
Return DirectCast(GetValue(Text1Property), String)
End Get
Set
SetValue(Text1Property, Value)
End Set
End Property
Public Shared ReadOnly Text2Property As DependencyProperty = DependencyProperty.Register(
"Text2", GetType(String), GetType(MainWindow), New PropertyMetadata(Nothing))
Public Property Text2 As String
Get
Return DirectCast(GetValue(Text2Property), String)
End Get
Set
SetValue(Text2Property, Value)
End Set
End Property
Private Sub MainWindow_OnLoaded(sender As Object, e As RoutedEventArgs)
Me.Text1 = "text1"
Me.Text2 = "text2"
End Sub
End Class
Converter:
Class MyConverter
Implements IMultiValueConverter
Public Function Convert(values As Object(), targetType As Type, parameter As Object, culture As CultureInfo) _
As Object Implements IMultiValueConverter.Convert
If values Is Nothing Then
Return DependencyProperty.UnsetValue
End If
Dim sb As New StringBuilder
If values.Length > 0 Then
sb.AppendLine(values(0))
End If
If values.Length > 1 Then
sb.AppendLine(values(1))
End If
Return sb.ToString()
End Function
Public Function ConvertBack(value As Object, targetTypes As Type(), parameter As Object, culture As CultureInfo) _
As Object() Implements IMultiValueConverter.ConvertBack
Throw New NotImplementedException
End Function
End Class
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:local="clr-namespace:WpfApplication2"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Name="Window"
Title="MainWindow"
Width="525"
Height="350"
Loaded="MainWindow_OnLoaded"
mc:Ignorable="d">
<Grid>
<StackPanel>
<TextBox Text="{Binding ElementName=Window, Path=Text1, UpdateSourceTrigger=PropertyChanged}" />
<TextBox Text="{Binding ElementName=Window, Path=Text2, UpdateSourceTrigger=PropertyChanged}" />
<Label>
<Label.Resources>
<local:MyConverter x:Key="MyConverter" />
</Label.Resources>
<Label.Content>
<MultiBinding Converter="{StaticResource MyConverter}">
<Binding ElementName="Window" Path="Text1" />
<Binding ElementName="Window" Path="Text2" />
</MultiBinding>
</Label.Content>
</Label>
</StackPanel>
</Grid>
</Window>
I mostly do databinding on ComboBoxes and DataGrids, and I assumed a ListBox would work the same, but it appears I'm missing something since my ListBox remains blank as I add Scan objects to my observable collection. Any idea what I'm missing here?
<ListBox Height="480" HorizontalAlignment="Left" Margin="551,77,0,0" Name="ListBox_scans" VerticalAlignment="Top" Width="415" ItemsSource="{Binding}" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=time}" />
<TextBlock Text="{Binding Path=station_name}"/>
<TextBlock Text="{Binding Path=Barcode}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Scan Class:
Public Class Scan
Public Property Barcode As String
Public FromStation As Station
Public DateTimeUTC As DateTime
Public ScanType As String 'LotID, Employee Badge, Break, Complete, Duplicate
Public Duplicate As Boolean = False
Public IsWIPCleanUp As Boolean = False
Public IsComplete As Boolean = False
Public Property station_name As String
Get
If (Not FromStation Is Nothing) Then
Return FromStation.Name
Else
Return "Station: No Station"
End If
End Get
Set(value As String)
End Set
End Property
Public Property time As String
Get
Return DateTimeUTC.ToLocalTime.ToShortTimeString()
End Get
Set(value As String)
End Set
End Property
End Class
Then I just bind in some main code with:
Private scan_collection As New ObservableCollection(Of Scan)
ListBox_scans.ItemsSource = scan_collection
I have a couple tables dealing with cars:
<Table(Name:="tblManufacturer")> Public Class dbManufacturers
Private _ManufacturerID As Integer
<Column(Storage:="_ManufacturerID", DbType:="int IDENTITY NOT NULL", IsPrimaryKey:=True, IsDbGenerated:=True, Name:="ManufacturerID")> _
Public Property ManufacturerID() As Integer
Get
Return Me._ManufacturerID
End Get
Set(value As Integer)
Me._ManufacturerID = value
End Set
End Property
Private _ManufacturerName As String
<Column(Storage:="_ManufacturerName", DbType:="Varchar(50)", Name:="ManufacturerName")> _
Public Property ManufacturerName() As String
Get
Return Me._ManufacturerName
End Get
Set(value As String)
Me._ManufacturerName = value
End Set
End Property
Private _Models As EntitySet(Of dbModels) = New EntitySet(Of dbModels)
<Association(Storage:="_Models", DeleteRule:="CASCADE", OtherKey:="ManufacturerID")> _
Public Property Models As EntitySet(Of dbModels)
Get
Return _Models
End Get
Set(value As EntitySet(Of dbModels))
_Models.Assign(value)
End Set
End Property
End Class
<Table(Name:="tblModels")> Public Class dbModels
Private _ModelID As Integer
<Column(Storage:="_ModelID", DbType:="int IDENTITY NOT NULL", IsPrimaryKey:=True, IsDbGenerated:=True, Name:="ModelID")> _
Public Property ModelID() As Integer
Get
Return Me._ModelID
End Get
Set(value As Integer)
Me._ModelID = value
End Set
End Property
Private _ManufacturerID As Integer
<Column(Storage:="_ManufacturerID", DbType:="int", Name:="ManufacturerID")> _
Public Property ManufacturerID() As Integer
Get
Return Me._ManufacturerID
End Get
Set(value As Integer)
Me._ManufacturerID = value
End Set
End Property
Private _ModelName As String
<Column(Storage:="_ModelName", DbType:="Varchar(200)", Name:="ModelName")> _
Public Property ModelName() As String
Get
Return Me._ModelName
End Get
Set(value As String)
Me._ModelName = value
End Set
End Property
End Class
The XAML:
<TreeView Name="TreeView1" Margin="3" ItemsSource="{Binding ElementName=ManufacturerInfo, Path=tblManufacturers}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate>
<TextBlock Text="{Binding Path=ManufacturerName}" />
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Source=Models, Path=ModelName}" />
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
The code behind:
Public Class ManufacturerInfo
Private LinqDB As New DataContext(My.Settings.dbConnection)
Property tblManufacturers As Table(Of dbManufacturers) = LinqDB.GetTable(Of dbManufacturers)()
Public Sub New()
InitializeComponent()
End Sub
Private Sub ManufacturerInfo_Loaded(sender As Object, e As System.Windows.RoutedEventArgs) Handles Me.Loaded
Me.DataContext = Me
End Sub
End Class
So I get the list of Manufacturers, but not the nested models. Is there a way to make this work, or do I have to change how I'm doing my LINQ query?
I'm having trouble finding similar treeview examples.
Figured it out.
Still have a bit to learn about the subtle art of data binding.
<TreeView Name="TreeView1" Margin="3" ItemsSource="{Binding ElementName=ManufacturerInfo, Path=tblManufacturers}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Models}">
<TextBlock Text="{Binding Path=ManufacturerName}" />
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=ModelName}" />
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
I have a Person and Address class. The Person class has properties like Name, Gender etc and Address. The Address class has properties like Street, City as strings. If I have a collection of Person called "people" which is a List(Of Person). And I bind "people" to a DataGrid in WPF. The Name column is fine, but the "City" column is always empty.
Class Address
Sub New()
End Sub
Private _city As String
Property City() As String
Get
Return _city
End Get
Set(ByVal value As String)
_city = vaule
End Set
End Property
End Class
Class Person
Sub New()
End Sub
Private _name As String = ""
Property Name() As String
Get
Return _name
End Get
Set(ByVal value As String)
_name = vaule
End Set
End Property
Private _address As Address
Property Address() As Address
Get
Return _address
End Get
Set(ByVal value As Address)
_address = vaule
End Set
End Property
End Class
Under the Window_Loaded event, I have
Dim people As List(of Person) = _DAL.GetAllPeople()
Me.myDataGrid.ItemsSource = people
Dim nameBinding As Binding = New Binding
nameBinding.Path = New PropertyPath("Name")
Me.nameColumn.Binding = nameBinding 'This works fine in datagrid
Dim addressBinding As Binding = New Binding
addressBinding.Path = New PropertyPath("Address.City")
Me.cityColumn.Binding = addressBinding 'This does not work
XAML CODE:
<DataGrid x:Name="myDataGrid" HorizontalAlignment="Left" Margin="8,177.4,0,38" Width="240" AutoGenerateColumns="False" >
<DataGrid.Columns>
<DataGridTextColumn x:Name="nameColumn" CanUserResize="False" IsReadOnly="True" Header="Name" Width="100"/>
<DataGridTextColumn x:Name="cityColumn" CanUserResize="False" Header="City" IsReadOnly="True" Width="*" />
</DataGrid.Columns>
</DataGrid>
I am attempting to bind a ObservableCollection to a ListView but when I attempt to debug the program VS2010 is throwing the exception "Window must be the root of the tree. Cannot add Window as a child of Visual"
If I was to remove the attempted ItemsSource binding the WPF window opens (obviously without any data in the listView). I have create a Sub in my codebehind that parses an XML file and adds values to the ObservableCollection, I then bind the ListView.
I have stepped through the code, and the error appears to be in the XAML as the Sub completes without error, the program errors after the CodeBehind has completed, everytime.
My XAML file is as follows:
<Window x:Class="test_ListView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="test_ListView" Height="300" Width="300">
<Grid>
<ListView Height="105" HorizontalAlignment="Left" Name="lst_EmergencyContacts" VerticalAlignment="Top" Width="478">
<ListView.View>
<GridView>
<GridViewColumn Header="Name" Width="160" />
<GridViewColumn DisplayMemberBinding="{Binding emContactsNumber}" Header="Number" Width="70" />
<GridViewColumn DisplayMemberBinding="{Binding emContactsPhoneCoverage}" Header="Phone Coverage" Width="95" />
<GridViewColumn DisplayMemberBinding="{Binding distListPhone}" Header="Distance" Width="60" />
<GridViewColumn Header="More" Width="60" />
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>
While I believe the error is within the XAML, I will include the CodeBehind details, just in case (as the program will run without the error if the Sub isnt called that binds the ListView).
Public Property emContactsName() As String
Get
Return em_Name
End Get
Set(ByVal value As String)
em_Name = value
End Set
End Property
Private em_Name As String
Public Property emContactsNumber() As String
Get
Return em_Number
End Get
Set(ByVal value As String)
em_Number = value
End Set
End Property
Private em_Number As String
Public Property emContacts27Mhz() As String
Get
Return em_27Mhz
End Get
Set(ByVal value As String)
em_27Mhz = value
End Set
End Property
Private em_27Mhz As String
Public Property emContactsUhf() As String
Get
Return em_Uhf
End Get
Set(ByVal value As String)
em_Uhf = value
End Set
End Property
Private em_Uhf As String
Public Property emContactsVhf() As String
Get
Return em_Vhf
End Get
Set(ByVal value As String)
em_Vhf = value
End Set
End Property
Private em_Vhf As String
Public Property IsSelected() As Boolean
Get
Return m_IsSelected
End Get
Set(ByVal value As Boolean)
m_IsSelected = value
End Set
End Property
Private m_IsSelected As Boolean
Private Sub Window_Loaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded
getEmergencyContactsData("\", "EmergencyContacts.xml")
End Sub
Private Sub getEmergencyContactsData(ByVal docPath As String, ByVal docName As String)
Try
Dim xDoc As XmlDocument = New XmlDocument
Dim nList As XmlNodeList
Dim node As XmlNode
xDoc.Load(docPath + docName)
nList = xDoc.SelectNodes("/items/item")
Dim emergencyContactsCollection As New ObservableCollection(Of test_googleLookup)()
For Each node In nList
Dim eID = node.Attributes.GetNamedItem("id").Value
Dim eName = node.ChildNodes.Item(0).InnerText
Dim eNumber = node.ChildNodes.Item(1).InnerText
Dim eRadio27Mhz = node.ChildNodes.Item(2).InnerText
Dim eRadioUhf = node.ChildNodes.Item(3).InnerText
Dim eRadioVhf = node.ChildNodes.Item(4).InnerText
emergencyContactsCollection.Add(New test_googleLookup() With {.emContactsName = eName, .emContactsNumber = eNumber, .emContacts27Mhz = eRadio27Mhz, .emContactsUhf = eRadioUhf, .emContactsVhf = eRadioVhf, .IsSelected = 0})
Next
' Add to resources
Resources("emergencyContactsCollection") = emergencyContactsCollection
lst_EmergencyContacts.ItemsSource = emergencyContactsCollection
Catch
End Try
End Sub
I can see when stepping through the debug that the XML file is being correctly parsed and the values being added to the ObservableCollection.
If anyone can provide any assistance it would be greatly appreciated.
Thanks.
Try changing emergencyContactsCollection into a public property (ie.,EmergencyContacts) of test_ListView. Set the DataContext of lst_EmergencyContacts to be EmergencyContactsCollection.
<Window x:Class="test_ListView" ... DataContext="{Binding RelativeSource={RelativeSource Self}}">
<ListView Name="lst_EmergencyContacts" ... DataContext="{Binding Path=EmergencyContacts}">
Managing your data binding in this way often makes things much easier. Even better would be to separate out the data bound Properties to their own ViewModel.
I ended up using ListBoxes, below is the code I used, in case anyone is interested:
<ListBox Grid.Row="1" Grid.Column="0" Grid.RowSpan="4" Grid.ColumnSpan="3" HorizontalAlignment="Stretch" ItemsSource="{DynamicResource emergencyContactsCollection}" Name="lst_emergencyServices" VerticalAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding emerContIsSelected, Mode=TwoWay}" />
<TextBlock Text="{Binding emerContName}" />
<TextBlock Text="{Binding emerContNumber}" />
<TextBlock Text="{Binding emerContRadio27mhz}" />
<TextBlock Text="{Binding emerContRadioUhf}" />
<TextBlock Text="{Binding emerContRadioVhf}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Thank you for your time and assistance.
Matt