Load WPF UserControl dynamically - wpf

I have an abstract base class aComponent and sub-classes CriteriaList, Question, etc.
For each sub-class I have two corresponding UserControls which have names like ueCriteriaList, ubCriteriaList, ueQuestion, ubQuestion, etc. (ue stands short for "UserControl with input elements", and ub for "UserControl with buttons".)
The DataContext is the "ControllerClass" with a property with my_aComponent as the getter for the actual aComponent instance. When the aComponent instance changes (for example to an instance of CriteriaList), I want to load the corresponding UserControls (in this case ueCriteriaList and ubCriteriaList).
I have two converters ueControlConverter and ubControlConverter which take the class name (e.g. CriteriaList) and return a UserControl instance (in this case, ueCriteriaList).
Public Class ueControlConverter
Implements IValueConverter
Public Function Convert(value As Object, targetType As Type, parameter As Object,
culture As System.Globalization.CultureInfo) As Object
Implements IValueConverter.Convert
Dim aComp As aComponent = value
Dim assemblyKlassenname As String = aComp.GetType.ToString
Dim assemblyName As String = Left(assemblyKlassenname,
assemblyKlassenname.IndexOf(".") + 1)
Dim klassenName As String = Right(assemblyKlassenname,
assemblyKlassenname.IndexOf(".") - 1)
Dim t As Type = Type.GetType(assemblyName & "ue" & klassenName)
Dim o As UserControl = Activator.CreateInstance(t)
o.DataContext = value
Return o
End Function
Public Function ConvertBack(value As Object, targetType As Type, parameter As Object,
culture As System.Globalization.CultureInfo) As Object
Implements IValueConverter.ConvertBack
Return value
End Function
End Class
In XAML I have two ContentControls which bind Content="{Binding Path=my_aComponent, Converter={StaticResource _ueControlConverter} and Content="{Binding Path=my_aComponent, Converter={StaticResource _ubControlConverter}. The right UserControls are shown but without binding to my_aComponent.
What can I do?

You need to also provide the DataContext property through binding for your ContentControl objects.

Related

vb.net programmatically added binding not working

I am trying to bind a variable value to a button's content property. i created a button named "button" inside a dockpanel of my main window in XAML.
<Button x:Name="button" Content="Button" Height="100"
VerticalAlignment="Top" Width="75"/>
Then I want to add a binding to a public variable test programmatically.
The initial value (400) is displayed correctly at runtime, but when I hit the "NextTurn" button to raise the Click event, the bound value isn't updated.
Imports System.Windows.Data
Class MainWindow
Public test As Integer
Public Sub New()
InitializeComponent()
Dim usr As New UserNS.User
mainUser = usr
test = 400
Dim btest As New Binding()
btest.Source = test
button.SetBinding(Button.ContentProperty, btest)
End Sub
Private Sub NextTurn_Click(sender As Object, e As RoutedEventArgs) Handles NextTurn.Click
test = test - 10
End Sub
End Class
Could you please help me?
Thank you very much!
First of all, fields cannot be bound, only properties.
The binding source should be an object which has the property you would like to bind.
Ideally it is not the form class itself but a separate class (aka. view model).
E.g. the main window (named MainWindow) can have a view model named MainViewModel.
This object must implement the INotifyPropertyChanged interface.
In the property setter you have to call a method which raises the PropertyChanged event that comes with INotifyPropertyChanged interface.
In my example it is:
Private Sub NotifyPropertyChanged(...)
IMPORTANT: VB.NET works in case-insensitive mode so avoid naming a Button control as button. Also if you implement a full property the backing field should have a different name. You cannot have a test field and a Test property at the same time. That's why I chose the _Test name for the field.
Here is a working example:
Imports System.ComponentModel
Imports System.Runtime.CompilerServices
Class MainWindow
Implements INotifyPropertyChanged
Public Sub New()
' Actually we can initialize the Test property here as well.
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
Test = 400
Dim bindingTest As New Binding() With {
.Source = Me, ' The object which has the property we want to bind.
.Mode = BindingMode.OneWay, ' We declare that the UI will accept changes from the object's property but not vica-versa.
.Path = New PropertyPath("Test") 'We have to pass the name of the property as a String value.
}
TestButton.SetBinding(Button.ContentProperty, bindingTest)
' We could also initialize the Test property here.
End Sub
' We can also initialize only the field instead of the property
' But new values must be set through the property setter.
Private _Test As Integer
Public Property Test() As Integer
Get
Return _Test
End Get
Set(ByVal value As Integer)
_Test = value
NotifyPropertyChanged()
End Set
End Property
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
' We use CallerMemberName attribute so that we do not need to pass the name of the property.
' The compiler will automatically pass the name of the caller property. In our case: "Test"
' To get it work we declare the parameter as Optional so that we really do not have to pass a parameter value.
Private Sub NotifyPropertyChanged(<CallerMemberName()> Optional ByVal propertyName As String = "")
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
Private Sub NextTurnButton_Click(sender As Object, e As RoutedEventArgs)
' You must set the property's value instead of the backing field!
Test = Test - 10
End Sub
End Class

Showing saved Xaml Canvas through binding

I'm saving an svg image as xaml string in a database. Next I record this xaml string in a property of a class (MyClass.XamlString) .
On my form I have a Canvas, and I want MyClass.XamlString to be the child of the canvas.
This is what I have:
Dim MyBinding As New Binding("XamlString")
MyBinding.Source = MyClass
MyBinding.Converter = New clsXamlToCanvasConverter
CanvasOnForm.SetBinding(ContentPresenter.ContentProperty, MyBinding)
and
Friend Class clsXamlToCanvasConverter
Implements IValueConverter
Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As Globalization.CultureInfo) As Object Implements IValueConverter.Convert
Dim ConvertedCanvas As Canvas = System.Windows.Markup.XamlReader.Parse("<Canvas xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"" xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml"">" & value & "</Canvas>")
Return ConvertedCanvas
End Function
Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As Globalization.CultureInfo) As Object Implements IValueConverter.ConvertBack
Throw New NotImplementedException
End Function
End Class
In debugging I see the clsXamlToCanvasConverter creates a canvas with children from the xaml string, this is working correctly although you might say the extra canvas is redundant.
My guess is this is where I'm going wrong: CanvasOnForm.SetBinding(ContentPresenter.ContentProperty, MyBinding)
The code runs fine, the problem is I don't see the image displayed.
I've been searching a lot on this and I've seen this post WPF What is the correct way of using SVG files as icons in WPF , but apparently this hasn't helped me out.
Any ideas?

Which textbox events fire when a value is being set from the ViewModel (MVVM)

I need to apply a format to the Text property through code behind in a custom control, i.e. intercept the value and format it. I cannot use StringFormat**.
I am looking for an event that will fire when the value is being set from the VM so I can intercept the value there.
**I am using UpdateSourceTrigger="PropertyChanged" and this doesnt work as expected with StringFormat
Custom controls don't generally map to a VM, or expose their templates directly for binding. Do you mean a UserControl?
If it really is a custom control, then you should expose a dependency property on your control for the VM to bind to. Then inside your control template you can bind the textbox to it too via a converter.
for requirements like these I would recommend you to have a converter
Text="{Binding YourTextValue, Converter={StaticResource FormatConverter}}"
and converter as -
Public Class FormatConverter
Implements IValueConverter
Public Function Convert(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As CultureInfo) As Object Implements IValueConverter.Convert
Return ' your formatted string
End Function
Public Function ConvertBack(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As CultureInfo) As Object Implements IValueConverter.ConvertBack
' do nothing. or may be if you want to
End Function
End Class

Given only a table name, get database data and edit it in a DataGrid?

I have a database table, but the schema changes too regularly to hard code the columns into the app (WPF app). How can I display all the rows from the table into a DataGrid and have it so the rows in the grid are editable?
Getting data from the database and updating the database I can do, but I'm having trouble trying to think of a way I can bind the contents of the DataGrid to some collection considering I can't program (for example) a model with the various columns as properties ahead of time - as the columns change quite frequently.
Has anyone come across this issue before?
Thanks
I have done this with a couple of converters which create a GridView for a ListView. I read the column names and the column data (formatted into strings) into arrays, and used these converters:
<ValueConversion(GetType(String()), GetType(GridView))>
Public Class TableToGridViewConverter
Implements IValueConverter
Private Shared ReadOnly ItemConverter As New ArrayToItemConverter
Public Function Convert(value As Object, targetType As System.Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.Convert
Dim header() As String = TryCast(value, String())
Dim result As New GridView
If header IsNot Nothing Then
For i As Integer = 0 To header.Length - 1
result.Columns.Add(New GridViewColumn() With {.Header = header(i), .DisplayMemberBinding = New Binding() With {.Converter = ItemConverter, .ConverterParameter = i}})
Next
End If
Return result
End Function
Public Function ConvertBack(value As Object, targetType As System.Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.ConvertBack
Throw New NotSupportedException
End Function
End Class
<ValueConversion(GetType(String()), GetType(String))>
Public Class ArrayToItemConverter
Implements IValueConverter
Public Function Convert(value As Object, targetType As System.Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.Convert
Dim result As String = String.Empty
If TypeOf value Is String() AndAlso IsNumeric(parameter) Then
Dim values() As String = value
Dim index As Integer = CInt(parameter)
If index >= 0 And index < values.Length Then
result = values(index)
End If
End If
Return result
End Function
Public Function ConvertBack(value As Object, targetType As System.Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.ConvertBack
Throw New NotSupportedException
End Function
End Class
The trick is to create a GridViewColumn for each column in your array, and use a converter which binds to an array of values and selects the value for the respective column using the column index as ConverterParameter. You would use it like this:
<ListView x:Name="TableDataListView" View="{Binding Path=DisplayTable.Header, Converter={StaticResource tableToGridView}}" ItemsSource="{Binding Path=DisplayRows}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Top"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
where the bound properties DisplayTable.Header and DisplayRows would contain the string array with the column names, and an array of string arrays containing the actual data, respectively.
There might be a different and much simpler approach, if it fits your requirements: Just use a System.Data.DataTable to hold your data and set AutoGenerateColumns="True" on the DataGrid. In my case, I needed the arrays...

WPF ColorAnimation for a Brush property

I wonder if someone can help me - I've got a label which I need to be able to cross-fade between any 2 colors when a method is called in the code behind.
My best attempt so far:
Private OldColor as Color = Colors.White
Sub SetPulseColor(ByVal NewColor As Color)
Dim F As New Animation.ColorAnimation(OldColor, NewColor, New Duration(TimeSpan.Parse("00:00:01")))
OldColor = NewColor
F.AutoReverse = False
PulseLogo.BeginAnimation(Label.ForegroundProperty, F)
End Sub
The problem I have is that ColorAnimation returns a Media.Color and The property type for Foreground is Brush.
I know how to create the appropriate brush but not how to do it in an animation.
From Googling, it seems I need a converter:
<ValueConversion(GetType(SolidColorBrush), GetType(SolidColorBrush))> _
Public Class ColorConverter
Implements IValueConverter
Public Function Convert(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.Convert
Dim Color As Color = DirectCast(value, Color)
Return New SolidColorBrush(Color)
End Function
Public Function ConvertBack(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.ConvertBack
Return Nothing
End Function
End Class
but all the examples I've seen bind it to the animation in XAML - And I'd like to do it in the code behind...
Can someone please point me in the right direction?
Thanks
The usual solution to this is not to use a converter, but instead to animate the Color of the Brush. However, to do this you need a PropertyPath, which in turn means you need a storyboard:
Storyboard s = new Storyboard();
s.Duration = new Duration(new TimeSpan(0, 0, 1));
s.Children.Add(F);
Storyboard.SetTarget(F, PulseLogo);
Storyboard.SetTargetProperty(F, new PropertyPath("Foreground.Color"));
s.Begin();
(pardon C# syntax)
Note the property path in the SetTargetProperty call, which traverses down through the Foreground property and into the resulting brush's Color property.
You can also use this technique to animate individual gradient stops in a gradient brush, etc.
ColorAnimation colorChangeAnimation = new ColorAnimation();
colorChangeAnimation.From = VariableColour;
colorChangeAnimation.To = BaseColour;
colorChangeAnimation.Duration = timeSpan;
PropertyPath colorTargetPath = new PropertyPath("(Panel.Background).(SolidColorBrush.Color)");
Storyboard CellBackgroundChangeStory = new Storyboard();
Storyboard.SetTarget(colorChangeAnimation, BackGroundCellGrid);
Storyboard.SetTargetProperty(colorChangeAnimation, colorTargetPath);
CellBackgroundChangeStory.Children.Add(colorChangeAnimation);
CellBackgroundChangeStory.Begin();
//VariableColour & BaseColour are class of Color, timeSpan is Class of TimeSpan, BackGroundCellGrid is class of Grid;
//no need to create SolidColorBrush and binding to it in XAML;
//have fun!

Resources