I am currently working on a multilingual app where the interface text can be swapped at runtime based on the language that is selected by the user. I am using DynamicResources defined in a ResourceDictionary and swapping the dictionary file when the language is changed. This works great for everything except for the DataGrid's Column Header property. I know that the DataGrid column is not part of the Visual Tree and have used proxies for binding to properties in my VM in the past however, in this case there is no binding to the VM. How can I go about updating the column headers when the ResourceDictionary is swapped?
My method for swapping dictionaries is below. This resides in the Application.xaml.vb and is called upon app startup passing a string saved in MySettings.Default. This is also called using a messenger from a property in a VM that is bound to a ComboBoxSelectedIndex.
Private Sub SetLanguage(ByVal language As String)
Dim dic As ResourceDictionary = Nothing
Dim langFile As String = Environment.CurrentDirectory & "\Languages\" & language & ".xaml"
If File.Exists(langFile) Then
Using fs As FileStream = New FileStream(langFile, FileMode.Open)
dic = CType(XamlReader.Load(fs), ResourceDictionary)
If LanguageCount > 0 Then
Resources.MergedDictionaries.RemoveAt(Resources.MergedDictionaries.Count - 1)
End If
Resources.MergedDictionaries.Add(dic)
End Using
End If
LanguageCount += 1
End Sub
The related DataGrid xaml
<DataGridTextColumn Header="{DynamicResource G_Spec}" ... />
The ResourceDictionary entry
<system:String x:Key="G_Spec">Spec:</system:String>
This is an extremely simplified example, basically you can use HeaderTemplate for the column:
<DataGridTemplateColumn>
<DataGridTemplateColumn.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{DynamicResource MyColumnHeaderText}" />
</DataTemplate>
</DataGridTemplateColumn.HeaderTemplate>
</DataGridTemplateColumn>
Related
I have WPF Form where I've created a listview from a childdataview. The listview, amongst other controls, will have an image.
So the datarows from the childdataview have the "Image Name", the location is actually inside a seperate zip file.
I've accomplished the display of the pictures just fine in winforms:
If img.newFileFlag = vbTrue Then
photoDisp.Image = Image.FromFile(img.newFileLocation)
photoDisp.SizeMode = Windows.Forms.PictureBoxSizeMode.StretchImage
Else
Dim bp_Name As String = loc
Dim strm As FileStream = New FileStream(bp_Name, FileMode.Open)
Dim archive As ZipArchive = New ZipArchive(strm, ZipArchiveMode.Update)
Dim imageStrm As Stream
For Each entry As ZipArchiveEntry In archive.Entries
If entry.Name.Contains(img.xFileName) Then
imageStrm = entry.Open()
photoDisp.Image = Image.FromStream(imageStrm)
photoDisp.SizeMode = Windows.Forms.PictureBoxSizeMode.StretchImage
Exit For
End If
Next
strm.Close()
End If
Write wrong or in between, it works. So i've taken on the task of converting over to WPF and trying to do a bit MVVM as well.
So if i have a listview:
<ListView Grid.Column="1" VerticalAlignment="Stretch" HorizontalAlignment="Left" Width="300" Background="Red" ItemsSource="{Binding Path=ConnectedChildView}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<Image Source="/"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
What's the correct way to get that Image loaded correctly?
In my Viewmodel I can get to the zip file:
Public Property BPDProp As BredpackageData
Get
Return BPD
End Get
Set(value As BredpackageData)
BPD = value
End Set
End Property
And in the BredpackageData Class:
Public Property BredPackageLocationProperty() As String
Get
Return BredPackageLocation
End Get
Set(ByVal value As String)
BredPackageLocation = value
End Set
End Property
So i have all the data necessary to use a similar function as my winforms, and I could probably just sneak it into the code behind and make it work, but that feels wrong.
Is there a better, more correct way?
I'd like to post more about what I've tried, but I haven't really tried anything. I can't get anywhere.
Thanks
Is it possible to display something in my treeview at design time and what would be the best way to achieve it?
Runtime works perfectly well and does display the data I want. However, I would like to have a more convenient display when designing than an empty area where the treeview is.
Thank you.
The treeview looks like this:
<TreeView x:Name="tvConfig" Width="400" Height="300" >
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type demo:TvItemsSource}" ItemsSource="{Binding Path=Items}">
<StackPanel Orientation="Horizontal">
<Image></Image>
<TextBlock Text="{Binding Name}"></TextBlock>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
And make uses of this class:
Public Class TvItemsSource
Public Property Items() As New List(Of TvItemsSource)
Public Property Name() As String
Public Sub New(Name As String)
Me.Name = Name
End Sub
End Class
I generated a small runtime data using the following piece of code:
Private _source As TvItemsSource
Private Sub LoadData()
_source = New TvItemsSource("Root")
_source.Items.Add(New TvItemsSource("Item 1"))
_source.Items.Add(New TvItemsSource("Item 2 "))
Dim ParentItem1 = New TvItemsSource("Parent 1")
ParentItem1.Items.Add(New TvItemsSource("Enfant 1"))
Dim ParentItem2 = New TvItemsSource("Parent 2")
ParentItem2.Items.Add(New TvItemsSource("Enfant 2"))
ParentItem1.Items.Add(ParentItem2)
_source.Items.Add(ParentItem1)
tvConfig.ItemsSource = _source.Items
End sub
edit: In the xaml of the treeview, I added the xmlns namespace of the application to declare the data type as :
xmlns:demo="clr-namespace:demo"
I just found the ideal solution for my project by using this statement:
Dim IsDesignMode As Boolean = DesignerProperties.GetIsInDesignMode(New DependencyObject())
The IsDesignMode variable does return true and anything therefore a listbox items source binded on a "Content" property would display the "Design Mode" items in the designer and the "Production" items when ran.
If IsDesignMode Then
For I = 1 To 10
Content.Add("DesignMode " & I)
Next
Else
Content.Add("Production 1")
Content.Add("Production 2")
Content.Add("Production 3")
End If
There are other way to do it.
Relevant:
SO: What approaches are available to dummy design-time data in WPF
Also:
Simulating data in design mode in Microsoft Blend
I'm creating a WPF data access layer that probably doesn't require full MVVM at this stage (but I might implement it).
I've successfully created a ComboBox that data binds to a foreign key value of a related table using a CollectionViewSource as the data source (See my XAML below, the combo box works fine but the TextBlock doesn't).
I only want to display the ComboBox as the cell editing template and use a TextBlock for displaying the data when it is not being edited. I can get the TextBlock to almost work (it displays data from the table related in the Foreign Key) but I can't find the equivalent property for "SelectedValuePath" so the TextBlock always displays the first value from the related table, rather than the value that corresponds to the ID in the Foreign Key field.
Is there a way (there must be) to get an equivalent behaviour from the TextBlock as I have in the ComboBox? Is there an equivalent property for SelectedValuePath?
The answer to this question would be hugely useful as there are some other fields I want to display in my data grid without providing the user any ability to edit but I still want to display a field from a related table rather than the Foreign Key ID.
Thanks so much in advance for your help and have a great day!
<CollectionViewSource x:Key="QGradeLookup"/>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock
Text="{Binding Source={StaticResource QGradeLookup}, Path=QGrade}"
>
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox
IsEditable="False"
ItemsSource="{Binding Source={StaticResource QGradeLookup}}"
DisplayMemberPath="QGrade"
SelectedValuePath="ID"
SelectedValue="{Binding Path=OfficeQualityGradeID}"
IsSynchronizedWithCurrentItem="False"
>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
As requested here's the code for the item's source (thanks NIT):
Dim QGradeLookup As CollectionViewSource
Dim QGradeList = From q In OMRInterfaceEntities.OfficeQualityGrades
Dim QGsource = CType(Me.FindResource("QGradeLookup"), CollectionViewSource)
QGsource.Source = QGradeList.ToList()
I used Beth Massi's post as a template for the above - Beth Massi's Template
And here's the codebehind
Public Class WinPropertyDataEntry
Dim QGradeLookup As CollectionViewSource
Private Function GetOMRMarketsQuery(OMRInterfaceEntities As OMRInterfaceCustomCode.OMRInterfaceEntities) As System.Data.Objects.ObjectQuery(Of OMR.OMRInterfaceCustomCode.OMRMarket)
Dim OMRMarketsQuery As System.Data.Objects.ObjectQuery(Of OMR.OMRInterfaceCustomCode.OMRMarket) = OMRInterfaceEntities.OMRMarkets
'To explicitly load data, you may need to add Include methods like below:
'OMRMarketsQuery = OMRMarketsQuery.Include("OMRMarkets.OMRMarketType").
'For more information, please see http://go.microsoft.com/fwlink/?LinkId=157380
'Update the query to include Properties data in OMRMarkets. You can modify this code as needed.
OMRMarketsQuery = OMRMarketsQuery.Include("Properties")
'Update the query to include OMRBuildingSurveys data in OMRMarkets. You can modify this code as needed.
OMRMarketsQuery = OMRMarketsQuery.Include("Properties.OMRBuildingSurveys").Where("it.ID = 12")
'Returns an ObjectQuery.
Return OMRMarketsQuery
End Function
Private Sub Window_Loaded_1(sender As Object, e As RoutedEventArgs) Handles MyBase.Loaded
Dim OMRInterfaceEntities As OMR.OMRInterfaceCustomCode.OMRInterfaceEntities = New OMR.OMRInterfaceCustomCode.OMRInterfaceEntities()
'Load data into OMRMarkets. You can modify this code as needed.
Dim OMRMarketsViewSource As System.Windows.Data.CollectionViewSource = CType(Me.FindResource("OMRMarketsViewSource"), System.Windows.Data.CollectionViewSource)
Dim OMRMarketsQuery As System.Data.Objects.ObjectQuery(Of OMR.OMRInterfaceCustomCode.OMRMarket) = Me.GetOMRMarketsQuery(OMRInterfaceEntities)
OMRMarketsViewSource.Source = OMRMarketsQuery.Execute(System.Data.Objects.MergeOption.AppendOnly)
Dim QGradeList = From q In OMRInterfaceEntities.OfficeQualityGrades
Dim QGsource = CType(Me.FindResource("QGradeLookup"), CollectionViewSource)
QGsource.Source = QGradeList.ToList()
End Sub
The requirement you put here can be handled in the following manner. First of all binding the TextBlock here to Collection source and giving Path is not going to give you anything.
Each row in your DataGrid represents object of type OMRBuildingSurvey which has property OfficeQualityGradeID which I assume is of type string or int. Now what you want here is whenever the ID for OfficeQualityGrade is changed, the non-editable template i.e TextBlock should display the Grade name for the selected OfficeQualityGrade.
Problem here is you are not capturing the selected OfficeGrade here. So what you need to do is to first bind SelectedItem of the Combobox to the property of type "OfficeQualityGrade" (lets say SelectedOfficeGrade). The property should raise the property change notifications. And then you can bind your textblock to this property as Text = {Binding SelectedOfficeGrade.Grade}. you will have to define this property in your entity and implement INotifyPropertychanged.
Hope this will help.
Thanks
Okay, thanks so much Nit for your help. Your solution would absolutely work in a different scenario but each building survey has it's own unique Quality Grade. The answer is that I was referencing the object incorrectly. I was using the name of the column (OfficeQualityGradeID) Rather than the name of the object OfficeQualityGrade.
The following code provides a text block for the display of Quality Grades and a Combo Box for editing Quality Grades:
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock
Text="{Binding OfficeQualityGrade.QGrade}"
/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox
x:Name="QGradeSelector"
IsEditable="False"
ItemsSource="{Binding Source={StaticResource QGradeLookup}}"
DisplayMemberPath="QGrade"
SelectedValuePath="ID"
SelectedValue="{Binding Path=OfficeQualityGradeID}"
IsSynchronizedWithCurrentItem="False"
>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
My application has a couple of ObservableCollections, one which is nested within an element of the other. Each contain a number of fields e.g.
ObservableCollectionA (called Listings)
Title
Description
Address
Images As MediaItems
ObservableCollectionB (called MediaItems)
ImageName
Src
DisplayTime
Currently I have been accessing ObservableCollections as follows:
Listings(0).MediaItems(0).ImageName
My application has the main Window display the items from Listings and a UserControl which contains a ListBox which displays the items from MediaItems.
Currently my Window is bound to Listings using code in the New method:
Dim AppLocal As Program = Application.CurrentItem
AppLocal.CurrentItem = 0
Me.DataContext = Listings.Item(AppLocal.CurrentItem)
For the Listings ObservableCollection, the UserControl has a XAML DataContext which references a local method which pulls the records from the nested MediaItems ObservableCollection.
<UserControl.DataContext>
<ObjectDataProvider ObjectType="{x:Type local:ThumbImageLoader}" MethodName="LoadImagesv2" IsAsynchronous="True" />
</UserControl.DataContext>
<Grid x:Name="ThumbListBoxGrid">
<ListBox x:Name="ThumbListBox" ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True" />
</Grid>
The method is here:
Public NotInheritable Class ThumbImageLoader
Public Shared Function LoadImagesv2() As List(Of MediaItems)
Dim AppLocal As Program = Application.Current
Dim ThumbImages As New List(Of MediaItems)
ThumbImages = Listings(AppLocal.CurrentItem).MediaItems
Return ThumbImages
End Function
End Class
Whilst developing the UI layout I have just been binding the first item (0 index). I now want to be able to set AppLocal.CurrentItem from anywhere in the application so the Window and the ListBox are updated.
Ideally I would like it so when the global property index changes, the UI is updated.
How do I do this?
Is there a better way to go about it?
Ben
Ok, I discovered the joy of CollectionView. Offered exactly what I was after and was excrutiatingly easy to implement. I was blown away at not only how easy it was to implement, but I managed to cut out more lines of code than I used to implement it.
I implemented a public CollectionViewSource
Public ListingDataView As CollectionViewSource
In my main Window, I implemeted it as follows:
<CollectionViewSource x:Key="ListingDataView" />
and bound my top-level Grid to it:
<Grid DataContext="{Binding Source={StaticResource ListingDataView}}">
In my Application Startup I set the CollectionView Source
AppLocal.ListingDataView = CType(Application.Current.MainWindow.Resources("ListingDataView"), CollectionViewSource)
AppLocal.ListingDataView.Source = Listings
The next part which impressed me the most was implementing it for my User Control. I remembered the UserControl is inheriting from the main window so it has access to the CollectionView already, so I ditched the separate Class and Method binding in favour for this:
<ListBox ItemsSource="{Binding Path=MediaItems}" VerticalAlignment="Top" IsSynchronizedWithCurrentItem="True" />
Now whene I want to set the Current List Index, I simply call this:
AppLocal.ListingDataView.View.MoveCurrentToPosition(AppLocal.CurrentProperty)
A few milliseconds later, the UI updates automatically.
Done!!
When you want multiple source data (like your observable collection properties and the index for the observable collection) to a single target you should use MultiBinding.
So in your case somethign like this should help...
<ListBox x:Name="ThumbListBox" IsSynchronizedWithCurrentItem="True" >
<ListBox.ItemsSource>
<MultiBinding Converter="{StaticResource CollectionAndIndexCollaborator}">
<Binding Path="Listings" />
<Binding Path="Application.CurrentItem.CurrentItem" />
</MultiBinding>
</ListBox.ItemsSource>
</ListBox>
provided that ....
Your data context is some class that holds the Application object via a property of same name Application and the Listings collection via property of same name Listings.
Your DataContext class and Application class must have INotifyPropertyChanged implemented. It should also raise notifications for Application and Setter of CurrentItem and CurrentItem.CurrentItem properties.
CollectionAndIndexCollaborator.Convert() method returns the same indexed value as the final collection....
return ((ObservableCollection)values[0]).Item(int.Parse(values[1])) ;
where assuming MyListingType is the T of your Listings collection.
This way when anyone changes the global Application.CurrentItem.CurrentItem the multi binding above will get notified and will select the required Listings item.
I would like to be able to programmatically create DataGridTemplateColumns based on my data source. For example, if my source has a date in a particular column I would like to be able to utilize a Datepicker control. I know this is easily accomplished with xaml and a DataGridTemplateColumn at design-time, however, how would I accomplish this at run-time?
Is my best option xamlreader.load or a more traditional route like:
Dim TempCol As Microsoft.Windows.Controls.DataGridTemplateColumn
I have not had any success with the latter.
Thanks.
-Paul
Edit:
This is the code I attempted to use:
Dim TempCol As New Microsoft.Windows.Controls.DataGridTemplateColumn
TempCol.CellEditingTemplate = DataTemplate.Equals(DatePicker)
I receive DatePicker is a type and cannot be used as an expression.
I am basiing this on the WPF Toolkit demo.
http://windowsclient.net/wpf/wpf35/wpf-35sp1-toolkit-datagrid-feature-walkthrough.aspx
<dg:DataGridTemplateColumn Header="Date" MinWidth="100">
<dg:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<dg:DatePicker SelectedDate="{Binding Date}" SelectedDateFormat="Short" />
</DataTemplate>
</dg:DataGridTemplateColumn.CellEditingTemplate>
<dg:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Date, StringFormat=d}" />
</DataTemplate>
</dg:DataGridTemplateColumn.CellTemplate>
</dg:DataGridTemplateColumn>
Thanks!
The reason that your code does not work is because you are setting the value of the CellEditingTemplate column to a bool (the result of calling DataTemplate.Equals(), rather than creating an instance of the template in code.
You can create a template in code using something like this (equivalent to the XAML code snippet you provided):
DataGridTemplateColumn col = new DataGridTemplateColumn();
col.Header = "Date";
// Create a factory. This will create the controls in each cell of this
// column as needed.
FrameworkElementFactory factory =
new FrameworkElementFactory(typeof(DatePicker));
// Bind the value of this cell to the value of the Date property of the
// DataContext of this row. The StringFormat "d" will be used to display
// the value.
Binding b = new Binding("Date");
b.StringFormat = "d";
factory.SetValue(DatePicker.SelectedDateProperty, b);
// Create the template itself, and add the factory to it.
DataTemplate cellEditingTemplate = new DataTemplate();
cellEditingTemplate.VisualTree = factory;
col.CellEditingTemplate = cellEditingTemplate;
I'm not sure if this approach would work better than loading the XAML yourself. Maybe try both approaches and see which one works best for you, and works faster?