I have a listView of items which are 'Book' instances, and when I click on a book the combobox should display its keywords; in fact it's a little trickier : the combobox contains the list of all the keywords of all books (duplicates removed)(the comboboxItems are checkboxes), and those of the selected book are checked.
here is the multibinding:
<ComboBox
x:Name="cbb_Keywords"
Grid.Column="2"
Width="300"
Margin="5,0,0,0"
HorizontalAlignment="Left"
ItemsSource="{Binding Source={StaticResource AllBooks}}"
DataContext="{Binding ElementName=listBoxBooks,Path=SelectedItem,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Width="200">
<CheckBox.IsChecked>
<MultiBinding Converter="{StaticResource TextInListTrueFalseConverter}" >
<Binding Path="KeywordsForTextbox"></Binding>
<Binding RelativeSource="{RelativeSource Self}" Path="Content"></Binding>
</MultiBinding>
</CheckBox.IsChecked>
</CheckBox>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
When I run my program, is seems ok when I click on a book, but I get an exception when I click on the combobox : impossible cast from 'MS.Internal.NamedObject' to 'System.String' type. I saw that value[0] is UnsetValue.
At debugging, when I use spies to track the value of WpfApp1.App.Books[0].KeywordsForTextbox, it gives me the good value (a string which is the list of the keywords of Book[0]. maybe the problem comes from listboxBooks.SelectedItem.KeywordsForTextBox? I can't spy in VS the value of 'listboxBooks'.
some related content...
the beginning of the constructor of MainWindow:
public MainWindow()
{
InitializeComponent();
listBoxBooks.ItemsSource = App.Books;
the convert method of the converter:
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var check = false;
if ((values != null && values.Length == 2))
{
string listString = (string)values[0];
string wordToFind = (string) values[1];
if ((listString != null))
{
List<string> keywordsList = listString.Split(',').ToList();
if (keywordsList.Contains(wordToFind)) check = true;
}
}
return check;
}
the KeywordsForTextbox method:
public string KeywordsForTextbox
{
get { return string.Join(",", _keywords); }
}
and finally the implementation of AllBooks:(as a window resource)
<ObjectDataProvider
x:Key="AllBooks"
MethodName="listOfAllKeywords"
ObjectType="{x:Type mangmt:BookManagement}" />
thank you.
The first Binding of the Multi should be to be to the SelectedItem in the ListBox of Books. I have added in the <CheckBox.IsChecked> where appropriate, and Content="{Binding}" to the CheckBox:
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Width="200" Content={Binding}>
<CheckBox.IsChecked>
<MultiBinding Converter="{StaticResource TextInListTrueFalseConverter}" >
<Binding ElementName=listBoxBooks, Path=SelectedItem.KeywordsForTextbox"></Binding>
<Binding RelativeSource="{RelativeSource Self}" Path="Content"></Binding>
</MultiBinding>
</CheckBox.IsChecked>
</CheckBox>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
You may also wish to add some validation to the IMultiValueConverter to make sure the passed values are not unset, to avoid an exception: If Not values(0) Is DependencyProperty.UnsetValue And Not values(1) Is DependencyProperty.UnsetValue Then in VB.
Regarding the behaviour on checking the checkbox, I am guessing this is because of the ConvertBack Method of the IMultiValueConverter. You can remove the 'Throw Exception' code, and write a method to add/remove the text of the checked/unchecked box to your keyword list.
Related
I am loading items from database to my DataGrid on this way (on form load):
dataGridArticles.ItemsSource = null;
dataGridArticles.ItemsSource = listCurrentArticles;
And I got ability to add item to my datagrid list, and also I have ability to remove them by pressing DEL key <datagrid automatically removes it from a list>.
But When I am entering item to my list which is source to datagrid I am also setting OrdinalNumber for each item, for example when I adding new item to a list I am doing next thing:
article.OrdinalNumber = GetCurrentlyMaxOrdinalNumber()+1;
so if I'm adding items one after other, ordinals numbers will be 1,2,3,4,5... and so on, something like this (marked red) :
My stored procedure which is returning max ordinal number from articles table:
CREATE DEFINER=`root`#`localhost` PROCEDURE `ArticleGetNextCodeNumber`()
BEGIN
Select ifnull(max(OriginalArticleCode),0)
from articles;
END
And this is what is happening when I am adding new item to my list:
I am setting next ordinal numbers and some other attributes for new article:
private void btnSaveChangesOnArticle_Click(object sender, RoutedEventArgs e)
{
try
{
if (newItem)
{
Article newArticle = new Article();
newArticle.ArticleCode = txtArticleCode.Text.Trim();
newArticle.OriginalArticleCode = Convert.ToInt32(ArticlesController.Instance.GetNextArticleCode()) + 1; // Here I'm setting new article ordinal number
newArticle.Title = txtArticleTitle.Text.Trim();
newArticle.Price = Convert.ToDecimal(txtPrice.Text.Trim());
var lastInserted = ArticlesController.Instance.Save(newArticle);
MessageBox.Show("Operation Successful.");
listCurrentArticles.Add(lastInserted);
dataGridArticles.ItemsSource = null;
dataGridArticles.ItemsSource = listCurrentArticles;
}
}
}
}
And I am wondering right now, when user press DELETE KEY to remove some item from my datagrid,how can I reorganize my ordinal numbers, because if I add 5 items for example to my datagrid, and if user delete second item, than my order might look like this: 1 3 4 5.. insted of 1 2 3 4
So my question is guys, how can i reorganize that, when something changes to keep it sequenced all the time ( so maybe then I dont need to keep ordinal number in database, because it is not important to me, I am using it just because of better UI look, to show to user how many items he added to that DataGrid).
Thanks a lot,
Cheers!
You could replace the OrdinalNumber DataGridTextColumn with a DataGridTemplateColumn and use a converter to return the value of the GetIndex() method of the parent DataGridRow container.
You need to bind to both the parent DataGridRow and the Count property of the Items property of the DataGrid itself for the binding to get refreshed when the number of items changes, i.e. when an item is added or removed.
This should be pretty simple to implement. You create a class that implements the IMultiValueConverter interface:
namespace WpfApplication3
{
public class RowNumberConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return ((values[0] as DataGridRow).GetIndex() + 1).ToString();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
And use it like this:
<DataGrid x:Name="dataGridArticles" AutoGenerateColumns="False" xmlns:local="clr-namespace:WpfApplication3">
<DataGrid.Resources>
<local:RowNumberConverter x:Key="RowNumberConverter" />
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn Header="#" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource RowNumberConverter}">
<Binding Path="." RelativeSource="{RelativeSource AncestorType=DataGridRow}" />
<Binding Path="Items.Count" RelativeSource="{RelativeSource AncestorType=DataGrid}" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<!-- + the rest of your columns -->
</DataGrid.Columns>
</DataGrid>
Check out this related question that might solve your problem.
Column/Row index in a DataGrid column
Your best option is to remove OrdinalNumber property (Since you do not need it, as you said) and use DataGrid.RowHeaderTemplate with a converter. Here is a working example:
MainWindow
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
listCurrentArticles = new ObservableCollection<Article>() { new Article { Text = "k" }, new Article { Text = "l" } };
}
public ObservableCollection<Article> listCurrentArticles { get; set; }
Xaml
<Window.Resources>
<local:HeaderConverter x:Key="headerConverter"/>
</Window.Resources>
<Grid>
<DataGrid DataContext="{Binding}" ItemsSource="{Binding listCurrentArticles}">
<DataGrid.RowHeaderTemplate>
<DataTemplate>
<TextBlock MinWidth="25" TextAlignment="Center">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource headerConverter}">
<Binding Path="ItemsSource" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=DataGrid}" />
<Binding Path="Item" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=DataGridRow}"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</DataGrid.RowHeaderTemplate>
</DataGrid>
</Grid>
Converter
public class HeaderConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
ObservableCollection<Article> list = values[0] as ObservableCollection<Article>;
Article obj = values[1] as Article;
int ind = list.IndexOf(obj);
if (ind == -1)
return "+";
else
return (ind + 1).ToString();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
I have the following scenario: In my View Model I have two properties, let them be CreateNewThing and SelectedExistingThing. CreateNewThing is of type bool and SelectedExistingThing is of type Thing.
I now have a ComboBox that displays two static entries, "None" and "Create new", and a list of Things. I wired it up like this:
Collection for the list of things:
<ComboBox.Resources>
<CollectionViewSource x:Key="AllThings" Source="{Binding ViewModel.AllThings, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MyCustomControl}}}" />
<tools:ThingSelector x:Key="ThingSelector" />
</ComboBox.Resources>
Setup for the ItemsSource:
<ComboBox.ItemsSource>
<CompositeCollection>
<tools:PlaceholderForNone />
<tools:PlaceholderForNew />
<Separator />
<CollectionContainer Collection="{Binding Mode=OneWay, Source={StaticResource AllThings}}" />
</CompositeCollection>
</ComboBox.ItemsSource>
Binding for SelectedValue:
<ComboBox.SelectedValue>
<MultiBinding Converter="{StaticResource ThingSelector}">
<Binding Path="ViewModel.CreateNewThing" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type local:MyCustomControl}}" />
<Binding Path="ViewModel.SelectedExistingThing" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type local:MyCustomControl}}" />
</MultiBinding>
</ComboBox.SelectedValue>
My ThingSelector looks like this:
internal sealed class ThingSelector: IMultiValueConverter {
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {
if((values == null) ||
(values.Length != 2) ||
(!(values[0] is bool))) {
return Binding.DoNothing;
}
if((bool)values[0]) {
return new PlaceholderForNew();
}
if(values[1] is Thing) {
return values[1];
}
return new PlaceholderForNone();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) {
if(value is PlaceholderForNew) {
return new object[] { true, null };
}
return new object[] { false, value as Thing};
}
}
The PlaceholderForXXX are done in a way that their HashCode is always 0 and Equals is true for all objects of the same type, so all two PlaceholderForXXX are equal.
The strange thing is now: I can select the "None" and "Create new" options from my combo box, and they are correctly propagated (i.e., selecting the "new" option sets the CreateNewThing property to true and the SelectedExistingThing property to null).
However, when I select a Thing from the combo box, the ConvertBack method is invoked with the parameter value being null where, to my understanding, it should be the selected value of the ComboBox which is clearly not null (if I put an appropriate event into the code-behind, I really see that SelectedValue is a Thing and not null).
What am I missing?
Based on this previously answered question, I'm trying to create an IMultiValueConverter that will allow the Text property of a TextBox in WPF to be bound to the sum of several other TextBox values. I've mirrored the answer to the referenced question fairly strictly, yet when testing this I get an InvalidCastException. In the code below, the lines commented out are the code from the aforementioned answer. I did try running this with the var datatype instead of using double (I dislike var, just a preference), and received the
same error in the same place. I've tried changing the style of cast in various ways, including Convert.ToInt32, (int), and even int.Parse, but everything results in the same error, same location.
Does anybody have a clue as to what the problem with this could be? This is my first real foray into binding like this, so it could be I'm fundamentally misunderstanding it, but honestly don't think that's what it is...
public class AddListRecordsConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
double result = 0.0;
try
{
double[] doubleValues = values.Cast<double>().ToArray();
foreach (var doubleValue in doubleValues)
{
result += doubleValue;
}
//var doubleValues = values.Cast<double>().ToArray();
//var leftPart = string.Join(" x ", doubleValues);
//var rightPart = doubleValues.Sum().ToString();
//var result = string.Format("{0} = {1}", leftPart, rightPart);
//return result;
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
return result;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Destination TextBox:
<TextBox x:Name="allRecords" Style="{StaticResource dataEntryTextBox}" Grid.Column="1" Grid.Row="6">
<TextBox.Text>
<MultiBinding Converter="{StaticResource AddListRecordsConverter}">
<Binding ElementName="allRecordsOne" Path="Text"></Binding>
<Binding ElementName="allRecordsTwo" Path="Text"></Binding>
</MultiBinding>
</TextBox.Text>
</TextBox>
Source TextBoxes:
<TextBox x:Name="allRecordsOne" Style="{StaticResource dataEntryTextBox}" Grid.Column="0" Grid.Row="4" GotFocus="SelectAllOnFocus_GotFocus" LostFocus="allRecords_LostFocus" />
<TextBox x:Name="allRecordsTwo" Style="{StaticResource readOnlyTextBox}" Grid.Column="0" Grid.Row="5" Text="{Binding ElementName=allRecordsOne, Path=Text}" GotFocus="SelectAllOnFocus_GotFocus" LostFocus="allRecords_LostFocus" />
<TextBox x:Name="allRecordsThree" Style="{StaticResource readOnlyTextBox}" Grid.Column="0" Grid.Row="6" Text="{Binding ElementName=allRecordsOne, Path=Text}" GotFocus="SelectAllOnFocus_GotFocus" LostFocus="allRecords_LostFocus" />
I simplified your example. Note that I used Mode="OneWay" to avoid exception in ConvertBack method.
<StackPanel>
<TextBox x:Name="allRecords">
<TextBox.Text>
<MultiBinding Converter="{StaticResource AddListRecordsConverter}">
<Binding ElementName="allRecordsOne" Path="Text" Mode="OneWay"/>
<Binding ElementName="allRecordsTwo" Path="Text" Mode="OneWay"/>
</MultiBinding>
</TextBox.Text>
</TextBox>
<TextBox x:Name="allRecordsOne" />
<TextBox x:Name="allRecordsTwo" />
</StackPanel>
the issue with the converter is that it receives two empty strings (default value of text) as input (values) and can't handle them properly. it has to be more defensive
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
double val = 0.0;
double result = 0.0;
foreach (var txt in values)
{
if (double.TryParse(txt.ToString(), out val))
result += val;
else
return "NaN";
}
return result.ToString();
}
I am trying to bind to a dependency property of my user control from my user control however it doesn't seem to be working as the converter keeps throwing an unset dependency property error
The dependency property
public DateTime? DisplayedDate
{
get { return (DateTime?)base.GetValue(DisplayedDateProperty); }
set { base.SetValue(DisplayedDateProperty, value); }
}
public static readonly DependencyProperty DisplayedDateProperty =
DependencyProperty.Register("DisplayedDate", typeof(DateTime?), typeof(SideBarUser), new FrameworkPropertyMetadata()
{
BindsTwoWayByDefault = true,
DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
});
The XAML Binding
<UserControl.Resources>
<sys:Int32 x:Key="Test">1</sys:Int32>
<Converters:DateCountConverter x:Key="DateCountConverter"/>
</UserControl.Resources>
<TextBlock DataContext="{Binding RelativeSource={RelativeSource Self}}"
TextAlignment="Center">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource DateCountConverter}">
<Binding Path="DisplayedDate" />
<Binding Source="{StaticResource Test}" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
And finally the part it's failing at in the coverter
DateTime date = (DateTime)values[0];
All together yields
System.InvalidCastException
Specified cast is not valid.
at System.Windows.Data.MultiBindingExpression.TransferValue()
at System.Windows.Data.MultiBindingExpression.Transfer()
at System.Windows.Data.MultiBindingExpression.UpdateTarget(Boolean includeInnerBindings)
at System.Windows.Data.MultiBindingExpression.AttachToContext(Boolean lastChance)
at System.Windows.Data.MultiBindingExpression.MS.Internal.Data.IDataBindEngineClient.AttachToContext(Boolean lastChance)
at MS.Internal.Data.DataBindEngine.Task.Run(Boolean lastChance)
at MS.Internal.Data.DataBindEngine.Run(Object arg)
at MS.Internal.Data.DataBindEngine.OnLayoutUpdated(Object sender, EventArgs e)
at System.Windows.ContextLayoutManager.fireLayoutUpdateEvent()
at System.Windows.ContextLayoutManager.UpdateLayout()
at System.Windows.UIElement.UpdateLayout()
at System.Windows.Interop.HwndSource.SetLayoutSize()
at System.Windows.Interop.HwndSource.set_RootVisualInternal(Visual value)
at System.Windows.Interop.HwndSource.set_RootVisual(Visual value)
at MS.Internal.DeferredHwndSource.ProcessQueue(Object sender, EventArgs e)
I cannot seem to get this to work for the life of me. Am I missing something? When debugging with another instance of Visual Studio it comes up that it is an Unset Dependency property
Edit:
When I comment out everything and just have
<TextBlock Text="{Binding Path=DisplayedDate, RelativeSource={RelativeSource Self}}" />
It works just fine displaying the display date. My confusion level is too great to cope with right now
EDIT EDIT:
Converter code
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
DateTime? date = (DateTime?)values[0];
//ToDo move most of the logic inside AppointmentsViewModel class to handle date filtering
AppointmentsViewModel MyAppointments = new AppointmentsViewModel();
String Count;
int SelectionType = (int)values[1];
//Note To Self Make Enum
switch (SelectionType)
{
case 0:
Count = MyAppointments.Appointments.Where(x => date != null && x.Beginning.HasValue && date.HasValue
&& x.Beginning.Value.Month == date.Value.Month
&& x.Beginning.Value.Year == date.Value.Year ).Count().ToString();
break;
case 1:
Count = MyAppointments.Appointments.Where(x => date != null && x.Test.HasValue && date.HasValue
&& x.Test.Value.Month == date.Value.Month
&& x.Test.Value.Year == date.Value.Year).Count().ToString();
break;
//ETC
default:
Count = MyAppointments.Appointments.Where(x => date != null && x.End.HasValue
&& date.HasValue && x.End.Value.Month == date.Value.Month
&& x.End.Value.Year == date.Value.Year).Count().ToString();
break;
}
return Count;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
There are several problems with your code. I had to make some assumptions here, so hopefully I'm correct.
The converter
Your converter assumes that it will get 2 values of certain types. You want to be a little careful with that. Especially the first value, which comes from a binding, might be DependencyProperty.UnsetValue if the binding has not been set yet.
So, you probably want to check if the values are correct before you start doing the actual conversion, for example:
if (values.Length != 2 || !(values[0] is DateTime?)|| !(values[1] is int))
{
return DependencyProperty.UnsetValue;
}
You should not have your converter throw exceptions, because they are treated as uncaught run-time exceptions and will terminate your application unless you have some global exception handler (see this question).
The control
Now, I assume that your DisplayedDate property is defined on your UserControl. If so, then this line:
<TextBlock DataContext="{Binding RelativeSource={RelativeSource Self}}"
will set the DataContext to this TextBlock, so when you later go to retrieve the DisplayedDate property, it will not be found. You can fix this in 2 ways:
1) You use the ancestor-lookup binding:
"{Binding RelativeSource={RelativeSource AncestorType=local:UserControl1}}"
Of course, replace local:UserControl1 with the namespace and name of your control.
2) You define the content of the UserControl as a Template instead, then use {RelativeSource TemplatedParent}, which will point to the "owner" of the template, in this case, your UserControl:
<UserControl.Template>
<ControlTemplate>
<TextBlock DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}"
TextAlignment="Center">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource DateCountConverter}">
<Binding Path="DisplayedDate" />
<Binding Source="{StaticResource Test}" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</ControlTemplate>
</UserControl.Template>
Just put this in your XAML instead of the <TextBlock>...</TextBlock> part.
Maybe there are some other issues, but testing with a simple converter this worked for me.
I am relatively new in WPF and I face a problem.
I have to implement a form that gets the UI(xaml) from the database (as also the data).
Each of these forms that will be created at runtime they will have different controls.
Although I disagree with this approach I have to follow my boss directions.
The problem is with the validation.
We decided to do it with Validation Rules.
So I tried to implemented the basic example with the AgeRangeRule.
<TextBox Name="textBox1" Width="50" FontSize="15"
Validation.ErrorTemplate="{StaticResource validationTemplate}"
Style="{StaticResource textBoxInError}"
Grid.Row="1" Grid.Column="1" Margin="2">
<TextBox.Text>
<Binding Path="Age" Source="{StaticResource ods}"
UpdateSourceTrigger="PropertyChanged" >
<Binding.ValidationRules>
<c:AgeRangeRule Min="21" Max="130"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
The error that I get when I load the xaml is
Additional information: 'Cannot create unknown type '{clr-namespace:WpfDynamicTest1}AgeRangeRule'.'
And is in this line:
<c:AgeRangeRule Min="21" Max="130"/>
Note: c is defined as:
xmlns:c="clr-namespace:WpfDynamicTest1"
How can I overcome this error?
I faced similar errors with the ControlTemplate and Style for the errors but I moved them to the Application.xaml and my problems solved.
Can I do something similar with the reference to the class?
Edit: Additional Info:
How I load the xaml:
The "cell" form has these properties:
Public Property FormId() As Integer
Get
Return miFormId
End Get
Set(ByVal value As Integer)
miFormId = value
FormCharacteristics(value)
End Set
End Property
Public Property UI() As String
Get
Return msUI
End Get
Set(ByVal value As String)
msUI = value
Dim rootObject As DependencyObject = XamlReader.Parse(value)
Me.Content = rootObject
End Set
End Property
So when I call the form I do this:
Dim winD As New winDynamic
winD.FormId = 4
winD.Show()
The FormCharacteristics fills msUI and UI is loaded.
Though not sure if you search through some of the following links but i hope they could be of help to you:
Compile/Execute XAML during program runtime
WPF – dynamically compile and run event handlers within loose XAML using CodeDom
Loading XAML at runtime?
Error: 'Cannot create unknown type '{clr-namespace:NameSpace.Properties}Settings'.'
EDIT
Based on the links above, assuming you are using XamlReader, I created a sample and its working fine. In this case, the reason I found is, the XAML Parser need the ParserContext to map the namespaces to bind the required types at run time.
Xaml (Dynamic usercontrol to load)
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="300"
xmlns:c="clr-namespace:WpfApplication1">
<UserControl.Resources>
<c:MyDataSource x:Key="ods"/>
<ControlTemplate x:Key="validationTemplate">
<DockPanel>
<TextBlock Foreground="Red" FontSize="20">!</TextBlock>
<AdornedElementPlaceholder/>
</DockPanel>
</ControlTemplate>
<Style x:Key="textBoxInError" TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={x:Static RelativeSource.Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<StackPanel>
<TextBox Name="textBox1" Width="50" FontSize="15"
Validation.ErrorTemplate="{StaticResource validationTemplate}"
Style="{StaticResource textBoxInError}"
Grid.Row="1" Grid.Column="1" Margin="2">
<TextBox.Text>
<Binding Path="Age" Source="{StaticResource ods}"
UpdateSourceTrigger="PropertyChanged" >
<Binding.ValidationRules>
<c:AgeRangeRule Min="21" Max="130"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<Button x:Name="btnDynamic" Width="150" Height="30" Content="Click Me"/>
</StackPanel>
</UserControl>
Code behind (C#)
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
LoadXAML();
}
public void LoadXAML()
{
try
{
using (StreamReader xamlStream = new StreamReader(#"C:\WpfApplication1\WpfApplication1\DynamicWindow.xaml"))
{
var context = new ParserContext();
context.XamlTypeMapper = new XamlTypeMapper(new string[] { });
context.XmlnsDictionary.Add("c", "clr-namespace:WpfApplication1");
context.XamlTypeMapper.AddMappingProcessingInstruction("clr-namespace:WpfApplication1", "WpfApplication1", "WpfApplication1");
string xamlString = xamlStream .ReadToEnd();
DependencyObject rootObject = XamlReader.Parse(xamlString, context) as DependencyObject;
cntControl.Content = rootObject; //cntControl is a content control I placed inside MainWindow
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message.ToString());
}
}
}
Note
For the Binding Validation., I used same MSDN code you provided.
Also since I am not with the VB.NET HAT now, I choose C# for the code behind!! Though the code is simple enough.
Your AngeRangeRule should derive from ValidationRule.
public class AgeRangeRule : ValidationRule
{
....
}
And you have to override ValidationResult member:
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
// Cast value object and check if it is valid
return new ValidationResult(...,...);
}