WPF dynamically load xaml with validation rules - c#

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(...,...);
}

Related

TreeViewItem styles not being applied when selecting an item dynamically over via input

I have a treeview dynamically generated within the program. It uses properties on the class to select items by default if the user sets the preference for it:
However, when I do this, it applies the default style, rather than the current style, which is currently set and applies a AdonisUI dark mode style if requested, or light if not.
The Tree View (and Style) code:
<Window.Resources>
<Color x:Key="TitleBarColor">#FF191970</Color>
<Color x:Key="TitleBarForeColor">#FFFFFAF0</Color>
<Style x:Key="SystemTreeViewItemStyle" TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</Window.Resources>
....
<TreeView Name="tvwSystemTree" Grid.Column="0" Grid.Row="0"
SelectedItemChanged="tvwSystemTree_SelectedItemChanged" ItemContainerStyle="{StaticResource SystemTreeViewItemStyle}"
Visibility="Hidden">
<TreeView.Style>
<Style TargetType="TreeView" BasedOn="{StaticResource {x:Type TreeView}}"/>
</TreeView.Style>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate
DataType="{x:Type lAWObjects:SystemObject}" ItemsSource="{Binding Items}">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding IconUri, Mode=OneWay}" Height="16" Width="16" />
<TextBlock Text="{Binding Title}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
And in code-behind:
ObservableCollection<SystemObject> AstralObjects = new();
SystemObject root = new SystemObject() { Title = ourSystem.SystemName, IconUri = new Uri(SystemObject.SystemLogo) };
foreach(Star v in ourSystem.SystemStars)
{
SystemObject child = new SystemObject() { Title = v.Name, IconUri = new Uri(SystemObject.SunLogo) };
foreach (IOrbitalBody p in ourSystem.PlanetaryBodies)
{
if (p.Parent == v)
{
SystemObject child2 = new SystemObject() { Title = p.Name, IconUri = new Uri(SystemObject.PlanetLogo) };
child.Items.Add(child2);
}
}
root.Items.Add(child);
}
tvwSystemTree.ItemsSource = AstralObjects;
tvwSystemTree.Visibility= Visibility.Visible;
grdDetailView.Visibility = Visibility.Visible;
if (preferences.AutoDisplaySystem)
{
foreach (var v in AstralObjects)
{
if (v.Title == ourSystem.SystemName)
{
v.IsSelected = v.IsExpanded = true;
tvwSystemTree_SelectedItemChanged(this, new RoutedPropertyChangedEventArgs<object>(null, v));
}
}
}
For completion's sake, the SystemObject code that is probably most relevant is that it implements INotifyPropertyChanged. But I can provide it as well if requested.
When this code fires, it applies the normal blue-background and white-text. But if you click any option in the tree, it then applies the style specified colors.
I've tried specifiying that <Style x:Key="SystemTreeViewItemStyle" TargetType="{StaticResource {x:Type TreeViewItem}}"> but it appears AdonisUI doesn't support those properties on it. (And a code-search on github also appears to verify this.)
My only guess is that somehow the selection style is only applied on user interaction. Is there a way around this that I haven't figured out? I'm rather reluctant to apply explicit style colors so I don't have to create variations for any style I may apply in the future.
Update After some investigation I've found out it's because it's overriding Adonis's code (which makes sense) even if I attempt to apply it via {StaticResource {x:Key TreeViewItem}}, but is not respecting any changes I attempt to make via specified dynamic resource.

DependencyProperty.unsetValue in a multibinding

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.

C# WPF - How to Combine datatrigger and trigger?

I don't know if need to combine DataTrigger & Trigger, if there's better way please tell me.
My goal is, to create a menu(with icons), icons will change while meet hover or selected event.
Here's an enum define all menu types:
public enum PageTypes:byte
{
NotSet = 0,
HomePage = 1,
ShopPage = 2,
AboutPage = 3
}
Then I created a MenuItemModel represent each menu item:
public class MenuItemModel : INotifyPropertyChanged
{
private PageTypes _menuItemType = PageTypes.NotSet;
public PageTypes MenuItemType { get { return _menuItemType; } set { if (value != _menuItemType) { _menuItemType = value; RaisePropertyChanged(() => MenuItemType); } } }
private bool _isSelected = false;
public bool IsSelected { get { return _isSelected; } set { if (value != _isSelected) { _isSelected = value; RaisePropertyChanged(() => IsSelected); } } }
}
Ok, then I begin to create UI.
<!-- MenuItem Template -->
<DataTemplate x:Key="MenuTemplate">
<Button Command="{Binding ClickCommand}" CommandParameter="{Binding}">
<Image>
<Image.Style>
<Style TargetType="Image">
<Setter Property="Source" Value="/Image/Home_normal.png"/>
<Style.Triggers>
<DataTrigger Binding="{Binding MenuItemType}" Value="ShopPage">
<Setter Property="Source" Value="/Image/Shop_normal.png"/>
</DataTrigger>
<DataTrigger Binding="{Binding MenuItemType}" Value="AboutPage">
<Setter Property="Source" Value="/Image/About_normal.png"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</Button>
</DataTemplate>
till now everything is very easy, but when I try to make mouseOver and Selected effect, problem comes.
for example, if mouse over home_normal.png, it should change to home_hover.png, if IsSelected property is TRUE, image should be ignore hover trigger then use home_selected.png. But there's 3 image, how do I know what image should change?
<!-- MenuItem Template -->
<DataTemplate x:Key="MenuTemplate">
<Button Command="{Binding ClickCommand}" CommandParameter="{Binding}">
<Image>
<Image.Style>
<Style TargetType="Image">
<Setter Property="Source" Value="/Image/Home_normal.png"/>
<Style.Triggers>
<DataTrigger Binding="{Binding MenuItemType}" Value="ShopPage">
<Setter Property="Source" Value="/Image/Shop_normal.png"/>
</DataTrigger>
<DataTrigger Binding="{Binding MenuItemType}" Value="AboutPage">
<Setter Property="Source" Value="/Image/About_normal.png"/>
</DataTrigger>
<!-- MY PLAN -->
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Source" Value="?_hover.png"/>
</Trigger>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<Setter Property="Source" Value="?_selected.png"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</Button>
</DataTemplate>
If you can see the question mark in "MY PLAN" comment, that would be my question: what should I do in the Value field?
You can use MultiDataTrigger like this. But you should add same 3 triggers for all types of pages. Note that next trigger overrides below and conditions works like logical AND.
<p:Style.Triggers xmlns:p="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<DataTrigger Binding="{Binding MenuItemType}" Value="ShopPage">
<Setter Property="Source" Value="/Image/Shop_normal.png"/>
</DataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding MenuItemType}" Value="ShopPage" />
<Condition Binding="{Binding RelativeSource={RelativeSource Mode=Self}, Path=IsMouseOver}" Value="true" />
</MultiDataTrigger.Conditions>
<Setter Property="Source" Value="/Image/Shop_MouseOver.png" />
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding MenuItemType}" Value="ShopPage" />
<Condition Binding="{Binding RelativeSource={RelativeSource Mode=Self}, Path=IsSelected}" Value="true" />
</MultiDataTrigger.Conditions>
<Setter Property="Source" Value="/Image/Shop_IsSelected.png" />
</MultiDataTrigger>
</p:Style.Triggers>
In my opinion, the answer you've already received and accepted is a good one. It's entirely XAML-based, which seems to be a primary goal in your scenario, and it should work very well. That said, the XAML-only solution is fairly verbose and involves a lot of redundant code. This is already seen in the scenario above where you have two buttons types, each with three possible states. And it will only get worse as you add button types and states.
If you are willing to do a little code-behind, I think you can accomplish the same effect but with a lot less redundancy.
Specifically, if you use <MultiBinding>, you can bind the relevant properties to a collection that can be used to look up the correct image source. In order for me to accomplish this, I needed to create a couple of small container types to store the lookup data, and of course the IMultiValueConverter implementation to use them:
Container types:
[ContentProperty("Elements")]
class BitmapImageArray
{
private readonly List<ButtonImageStates> _elements = new List<ButtonImageStates>();
public List<ButtonImageStates> Elements
{
get { return _elements; }
}
}
class ButtonImageStates
{
public string Key { get; set; }
public BitmapImage[] StateImages { get; set; }
}
Converter:
class OrderedFlagConverter : IMultiValueConverter
{
public object Convert(object[] values,
Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
BitmapImageArray imageData = (BitmapImageArray)parameter;
string type = (string)values[0];
foreach (ButtonImageStates buttonStates in imageData.Elements)
{
if (buttonStates.Key == type)
{
int index = 1;
while (index < values.Length)
{
if ((bool)values[index])
{
break;
}
index++;
}
return buttonStates.StateImages[index - 1];
}
}
return DependencyProperty.UnsetValue;
}
public object[] ConvertBack(object value,
Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
In your example, using the above might look something like this:
<DataTemplate x:Key="MenuTemplate">
<Button Command="{Binding ClickCommand}" CommandParameter="{Binding}">
<Image>
<Image.Source>
<MultiBinding>
<MultiBinding.Converter>
<l:OrderedFlagConverter/>
</MultiBinding.Converter>
<MultiBinding.ConverterParameter>
<l:BitmapImageArray>
<l:ButtonImageStates Key="ShopPage">
<l:ButtonImageStates.StateImages>
<x:Array Type="{x:Type BitmapImage}">
<BitmapImage UriSource="/Image/Shop_selected.png"/>
<BitmapImage UriSource="/Image/Shop_hover.png"/>
<BitmapImage UriSource="/Image/Shop_normal.png"/>
</x:Array>
</l:ButtonImageStates.StateImages>
</l:ButtonImageStates>
<l:ButtonImageStates Key="AboutPage">
<l:ButtonImageStates.StateImages>
<x:Array Type="{x:Type BitmapImage}">
<BitmapImage UriSource="/Image/About_selected.png"/>
<BitmapImage UriSource="/Image/About_hover.png"/>
<BitmapImage UriSource="/Image/About_normal.png"/>
</x:Array>
</l:ButtonImageStates.StateImages>
</l:ButtonImageStates>
</l:BitmapImageArray>
</MultiBinding.ConverterParameter>
<Binding Path="ButtonType"/>
<Binding Path="IsMouseOver" RelativeSource="{RelativeSource Self}"/>
<Binding Path="IsSelected"/>
</MultiBinding>
</Image.Source>
</Image>
</Button>
</DataTemplate>
The converter takes, as input, bindings to the properties that affect the visual state of the button. The first bound value is simply the type of the button; this is used to look up the correct array of button states for the button. The remaining bound values (you can have arbitrarily many in this approach) are flags that are searched; the images are stored in the same order as the flags, with one additional "default" image at the end (i.e. if no flags are set, the default image is returned).
In this way, adding new button types involves only adding a new ButtonImageStates object, specifying the correct key for that button type, and adding new button states involves only adding a single line to each button type's list: the BitmapImage reference that corresponds to the image for that state for that button type.
Doing it this way drastically cuts down on the amount of code one has to add as new button types and states are needed: a given button type need be mentioned in the XAML only once, and likewise each triggering property is mentioned only once. A XAML-only approach will require a lot of duplicated boilerplate, and the actual image file references will be scattered throughout the style declaration.
Here is a simple demo of the basic technique. Lacking a good MCVE to start with, I didn't want to waste time re-creating parts of the code that weren't strictly necessary for the purposes of a demonstration:
I only bothered to create four state images, and of course only wrote code to deal with four possible states: two each for two different button types.
I also didn't bother with putting this in a menu; I'm just using a plain ItemsControl to present the buttons.
Naturally, the view model is a degenerate class; I didn't bother with property-change notification, since it's not needed here. The example still works if you include that though.
Here are the images used in the example (I'm a programmer, not an artist…I considered not even bothering with image content, since that's also not strictly required to demonstrate the basic technique, but figured I could handle four basic images :) ):
These are added to the project in a "Resources" folder, with the Build Action set to Resource.
XAML:
<Window x:Class="TestSO34193266MultiTriggerBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:TestSO34193266MultiTriggerBinding"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<l:OrderedFlagConverter x:Key="orderedFlagConverter1"/>
<BitmapImage x:Key="bitmapRedNormal"
UriSource="pack://application:,,,/Resources/red_normal.png"/>
<BitmapImage x:Key="bitmapRedHover"
UriSource="pack://application:,,,/Resources/red_hover.png"/>
<BitmapImage x:Key="bitmapGreenNormal"
UriSource="pack://application:,,,/Resources/green_normal.png"/>
<BitmapImage x:Key="bitmapGreenHover"
UriSource="pack://application:,,,/Resources/green_hover.png"/>
<l:ViewModel x:Key="redViewModel" ButtonType="Red"/>
<l:ViewModel x:Key="greenViewModel" ButtonType="Green"/>
<x:Array x:Key="items" Type="{x:Type l:ViewModel}">
<StaticResource ResourceKey="redViewModel"/>
<StaticResource ResourceKey="greenViewModel"/>
</x:Array>
<x:Array x:Key="redButtonStates" Type="{x:Type BitmapImage}">
<StaticResource ResourceKey="bitmapRedHover"/>
<StaticResource ResourceKey="bitmapRedNormal"/>
</x:Array>
<x:Array x:Key="greenButtonStates" Type="{x:Type BitmapImage}">
<StaticResource ResourceKey="bitmapGreenHover"/>
<StaticResource ResourceKey="bitmapGreenNormal"/>
</x:Array>
<l:BitmapImageArray x:Key="allButtonStates">
<l:ButtonImageStates Key="Red" StateImages="{StaticResource redButtonStates}"/>
<l:ButtonImageStates Key="Green" StateImages="{StaticResource greenButtonStates}"/>
</l:BitmapImageArray>
<ItemsPanelTemplate x:Key="panelTemplate">
<StackPanel IsItemsHost="True" Orientation="Horizontal"/>
</ItemsPanelTemplate>
<DataTemplate x:Key="template" DataType="l:ViewModel">
<Button>
<Image Stretch="None">
<Image.Source>
<MultiBinding Converter="{StaticResource orderedFlagConverter1}"
ConverterParameter="{StaticResource allButtonStates}">
<Binding Path="ButtonType"/>
<Binding Path="IsMouseOver" RelativeSource="{RelativeSource Self}"/>
</MultiBinding>
</Image.Source>
</Image>
</Button>
</DataTemplate>
<!-- explicit namespace only for the benefit of Stack Overflow formatting -->
<p:Style TargetType="ItemsControl"
xmlns:p="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<Setter Property="ItemsSource" Value="{StaticResource items}"/>
<Setter Property="ItemsPanel" Value="{StaticResource panelTemplate}"/>
</p:Style>
</Window.Resources>
<StackPanel>
<ItemsControl ItemTemplate="{StaticResource template}"/>
</StackPanel>
</Window>
C#:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
class ViewModel
{
public string ButtonType { get; set; }
}
class OrderedFlagConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
BitmapImageArray imageData = (BitmapImageArray)parameter;
string type = (string)values[0];
foreach (ButtonImageStates buttonStates in imageData.Elements)
{
if (buttonStates.Key == type)
{
int index = 1;
while (index < values.Length)
{
if ((bool)values[index])
{
break;
}
index++;
}
return buttonStates.StateImages[index - 1];
}
}
return DependencyProperty.UnsetValue;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
[ContentProperty("Elements")]
class BitmapImageArray
{
private readonly List<ButtonImageStates> _elements = new List<ButtonImageStates>();
public List<ButtonImageStates> Elements
{
get { return _elements; }
}
}
class ButtonImageStates
{
public string Key { get; set; }
public BitmapImage[] StateImages { get; set; }
}
One minor note: for some reason I get in the XAML editor the following error message on the <Window> element declaration:
Collection property 'TestSO34193266MultiTriggerBinding.ButtonImageStates'.'StateImages' is null.
I've clearly failed to jump through some hoop the XAML editor wants me to clear with respect to the declaration and/or implementation of ButtonImageStates, but what that is I don't know. The code compiles and runs just fine, so I haven't bothered to try to figure that part out. It may well be the case that there's a better way to represent the map of button state images, but this way works and other than the spurious error seems fine to me.

How can I find a style trigger-embedded element by name in WPF?

First, the heart of the question: If an element is assigned as the Content of a ContentControl via a style trigger, I can't seem to find it by name.
Now, for more detail: I have a panel that varies greatly in its layout and functionality based on its data context, which is a bug from a bug depot. When that bug is null, it is a search form, when it is non-null, it is a simple viewer for properties of that bug. The XAML then look something like:
<ContentControl DataContext="...">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="Content">
<Setter.Value>
...
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding}" Value="{x:Null}">
<Setter Property="Content">
<StackPanel>
<TextBox Name="Waldo"/>
<Button .../>
</StackPanel>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
When the user clicks the button that sits alongside the text box, I get a callback in the code behind. From that point I'd like to be able to access various properties of the text box. The question is, where's Waldo? :)
In the code behind I have tried a few variants of the following, all with little success:
this.FindName("Waldo"); // Always returns null
I've seen a lot of discussion on this topic as it relates to templates but not as it relates to setting content directly with triggers. Maybe it's because I am violating all sorts of best practices by doing this :)
Thank you!
If an element is assigned as the Content of a ContentControl via a style trigger, I can't seem to find it by name.
If you needed to access to the Content before trigger occurs, it would most likely not possible. In this situation, the main thing to get access after the DataTrigger occurs.
I am violating all sorts of best practices by doing this
Maybe it's not the right way to work with the Сontrol in WPF, the more that you still need access to dynamic content, which can later be changed. But in any case, there are two ways to work with the Сontrol - it's like now and in the MVVM style. MVVM style is best suited for large and less complex applications with different business logic. If in your case for easy application, in this situation, I do not see anything wrong with that. In addition to doing a project in MVVM style need from scratch, combine conventional method and the correct method is not a good way.
I created a small example to demonstrate access controls for a given situation. There is a property that corresponds to the type of Content, the default is Init. If you assigns null for this property, the dynamic Content is loaded.
That's how I get access to TextBox:
private void GetAccessToTextBox_Click(object sender, RoutedEventArgs e)
{
TextBox MyTextBox = null;
StackPanel panel = MainContentControl.Content as StackPanel;
foreach (object child in panel.Children)
{
if (child is TextBox)
{
MyTextBox = child as TextBox;
}
}
if (MyTextBox != null)
{
MyTextBox.Background = Brushes.Gainsboro;
MyTextBox.Height = 100;
MyTextBox.Text = "Got access to me!";
}
}
Below it's a full example:
XAML
<Window x:Class="AccessToElementInContentControl.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:this="clr-namespace:AccessToElementInContentControl"
WindowStartupLocation="CenterScreen"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<this:TestData />
</Window.DataContext>
<Window.Resources>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="Content">
<Setter.Value>
<Label Content="InitContent"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=TypeContent}" Value="{x:Null}">
<Setter Property="Content">
<Setter.Value>
<StackPanel Name="NullStackPanel">
<TextBox Name="Waldo" Text="DynamicText" />
<Button Width="100" Height="30" Content="DynamicButton" />
</StackPanel>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<ContentControl Name="MainContentControl" />
<Button Name="SetContentType"
Width="100"
Height="30"
HorizontalAlignment="Left"
Content="SetContentType"
Click="SetContentType_Click" />
<Button Name="GetAccessToButton"
Width="110"
Height="30"
HorizontalAlignment="Right"
Content="GetAccessToTextBox"
Click="GetAccessToTextBox_Click" />
</Grid>
</Window>
Code-behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void SetContentType_Click(object sender, RoutedEventArgs e)
{
TestData test = this.DataContext as TestData;
test.TypeContent = null;
}
private void GetAccessToTextBox_Click(object sender, RoutedEventArgs e)
{
TextBox MyTextBox = null;
StackPanel panel = MainContentControl.Content as StackPanel;
foreach (object child in panel.Children)
{
if (child is TextBox)
{
MyTextBox = child as TextBox;
}
}
if (MyTextBox != null)
{
MyTextBox.Background = Brushes.Gainsboro;
MyTextBox.Height = 100;
MyTextBox.Text = "Got access to me!";
}
}
}
public class TestData : NotificationObject
{
private string _typeContent = "Init";
public string TypeContent
{
get
{
return _typeContent;
}
set
{
_typeContent = value;
NotifyPropertyChanged("TypeContent");
}
}
}
public class NotificationObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}

create a button programmatically with styles

Simple syntax question. Programming silverlight 4 on VS2010. I created a button style in xaml:
<UserControl.Resources>
<Style x:Key ="TestbuttonStyle" TargetType="Button">
<Setter Property="Width" Value="150"></Setter>
<Setter Property="Margin" Value="0,0,0,10"></Setter>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
<Image Source="http://i40.tinypic.com/j5k1kw.jpg" Height="20" Width="20" Margin="-30,0,0,0"></Image>
<TextBlock Text="sampleuser
sample number" Margin="5,0,0,0"></TextBlock>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
I need to create a button in the code behind, but using this style. I tried doign something like this:
Button btn = new Button();
//btn.Style = {TestbuttonStyle}; -what do i put here?
grid.children.add(btn);
how to I apply the style and add to my usercontrol grid?
Initially I thought you were working with WPF. Then I realized it's about Silverlight which doesn't have a hierarchical resource look-up helper method similar to WPF's FindResource or TryFindResource, respectively.
However, a quick search on the internet gave up this article which describes a nice extension method you can use:
public static object TryFindResource(this FrameworkElement element, object resourceKey)
{
var currentElement = element;
while (currentElement != null)
{
var resource = currentElement.Resources[resourceKey];
if (resource != null)
{
return resource;
}
currentElement = currentElement.Parent as FrameworkElement;
}
return Application.Current.Resources[resourceKey];
}
Then you can use it like this:
btn.Style = (Style)this.TryFindResource("TestbuttonStyle");

Categories