Set selectedItem in WPF TreeView with DataContext bound to XDocument - c#

I have TreeView in my WPF window. The TreeViews DataContext is bound to a XDocument.
I am in search for setting the selectedItem from code behind. As the nodes returned by for example .ItemContainerGenerator.Items are of type System.Xml.Linq.XElement and not System.Windows.Controls.TreeViewItem I can not just set isSelected :-(
Does anybody have an idea how to solve this?
EDIT:
XAML Code:
<Window.Resources>
<DataTemplate x:Key="AttributeTemplate">
<StackPanel Orientation="Horizontal"
Margin="3,0,0,0"
HorizontalAlignment="Center">
<TextBlock Text="{Binding Path=Name}"
Foreground="{StaticResource xmAttributeBrush}" FontFamily="Consolas" FontSize="8pt" />
<TextBlock Text="=""
Foreground="{StaticResource xmlMarkBrush}" FontFamily="Consolas" FontSize="8pt" />
<TextBlock Text="{Binding Path=Value, Mode=TwoWay}"
Foreground="{StaticResource xmlValueBrush}" FontFamily="Consolas" FontSize="8pt" />
<TextBlock Text="""
Foreground="{StaticResource xmlMarkBrush}" FontFamily="Consolas" FontSize="8pt" />
</StackPanel>
</DataTemplate>
<HierarchicalDataTemplate x:Key="NodeTemplate">
<StackPanel Orientation="Horizontal" Focusable="False">
<TextBlock x:Name="tbName" Text="Root" FontFamily="Consolas" FontSize="8pt" />
<ItemsControl
ItemTemplate="{StaticResource AttributeTemplate}" HorizontalAlignment="Center"
ItemsSource="{Binding Converter={StaticResource AttrConverter}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</StackPanel>
<HierarchicalDataTemplate.ItemsSource>
<Binding Path="Elements" />
</HierarchicalDataTemplate.ItemsSource>
<HierarchicalDataTemplate.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=NodeType}" Value="Element" />
<Condition Binding="{Binding Path=FirstNode.FirstNode.NodeType}" Value="Text" />
</MultiDataTrigger.Conditions>
<Setter TargetName="tbName" Property="Text" >
<Setter.Value>
<Binding Path="Name" />
</Setter.Value>
</Setter>
<Setter TargetName="tbName" Property="ToolTip">
<Setter.Value>
<MultiBinding Converter="{StaticResource MultiString2StringKonverter}">
<Binding Path="FirstNode.Value" />
<Binding Path="FirstNode.NextNode.Value" />
</MultiBinding>
</Setter.Value>
</Setter>
</MultiDataTrigger>
<DataTrigger Binding="{Binding Path=NodeType}" Value="Element">
<Setter TargetName="tbName" Property="Text" Value="{Binding Path=Name}" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=FirstNode.NodeType}" Value="Text">
<Setter TargetName="tbName" Property="Text">
<Setter.Value>
<MultiBinding StringFormat="{}{0} = {1}">
<Binding Path="Name"/>
<Binding Path="FirstNode.Value"/>
</MultiBinding>
</Setter.Value>
</Setter>
</DataTrigger>
</HierarchicalDataTemplate.Triggers>
</HierarchicalDataTemplate>
</Window.Resources>
<TreeView x:Name="LizenzAnsicht"
ItemsSource="{Binding Path=Root.Elements, UpdateSourceTrigger=PropertyChanged}"
ItemTemplate="{StaticResource ResourceKey=NodeTemplate}"
/>
Code Behind:
...
LizenzAnsicht.DataContext = XDocument.Load(<Path to XML-File>);

After sleeping a weekend about this I found an answer myself. There were some more problems as only the return type.
The solution is, to get the matching ItemContainer for the Item. This Container is of type TreeViewItem, where I can set isSelected an the like.
As an example is most of the time better for understanding, I give one. The following function will search for a XML- node in a TreeView- object and return a TreeViewItem, that contains the matching Node. If no Match is found null is returned.
The matching node is expanded.
The XML is handled in a System.Xml.linq.XDocument which is bound to the System.Windows.Controls.TreeView DataContext.
When calling the function The TreeView is set as ic.
public static TreeViewItem SearchNodeInTreeView(ItemsControl ic, XElement NodeDescription, string indent = "")
{
TreeViewItem ret = null;
foreach (object o in ic.Items)
{
if (o is XElement)
{
var x = ic.ItemContainerGenerator.ContainerFromItem(o);
if (XNode.DeepEquals((o as XElement), NodeDescription))
{
ret = x as TreeViewItem;
}
else if ((o as XElement).HasElements){
if (x != null)
{
bool expanded = (x as TreeViewItem).IsExpanded;
(x as TreeViewItem).IsExpanded = true;
(x as TreeViewItem).UpdateLayout();
ret = SearchNodeInTreeView (x as TreeViewItem, NodeDescription, indent + " ");
if (ret == null)
{
(x as TreeViewItem).IsExpanded = expanded;
(x as TreeViewItem).UpdateLayout();
}
}
}
}
if (ret != null)
{
break;
}
}
return ret;
}
I hope I can help anybody with this.

Related

Binding TextBoxes to a Button (MVVM)

I'm making an application to generate recipes for a specified period (for example a week). As part of this application I have to make a list of ingredients. These ingredients can be specified by the user so I made a UserControl for adding an ingredient. This UserControl contains 6 TextBox-es and a Button. What I'm trying to do is when the user click on the button, the ingredient specified by the user (and by the TextBoxes) will be added to the list of ingredients.
I'm using MVVM architecture and a MultiValueConverter to bind the TextBoxes to the Button. I noticed that the Convert method was called only at the instantiation, but not when I click the Button.
How can I solve this problem still using MVVM?
Thanks for your advice and time!
The corresponding code snippets:
AddIngredientView.xaml:
<UserControl.Resources>
<ResourceDictionary>
<converters:IngredientConverter x:Key="IngredientConverter"/>
</ResourceDictionary>
</UserControl.Resources>
<StackPanel>
<TextBlock Text="Étel hozzáadása" Foreground="White" FontSize="28" FontWeight="Bold" HorizontalAlignment="Center" Margin="0,0,0,20"/>
<TextBox Name="nameTextBox" ToolTip="Név" Width="250" Height="40" VerticalContentAlignment="Center" HorizontalAlignment="Center" Margin="5" Style="{StaticResource ModernTextbox}"/>
<TextBox Name="weightTextBox" ToolTip="Tömeg (g)" Width="250" Height="40" VerticalContentAlignment="Center" HorizontalAlignment="Center" Margin="5" Style="{StaticResource ModernTextbox}"/>
<TextBox Name="energyTextBox" ToolTip="Energia (kcal)" Width="250" Height="40" VerticalContentAlignment="Center" HorizontalAlignment="Center" Margin="5" Style="{StaticResource ModernTextbox}"/>
<TextBox Name="carbohydrateTextBox" ToolTip="Szénhidrát (g)" Width="250" Height="40" VerticalContentAlignment="Center" HorizontalAlignment="Center" Margin="5" Style="{StaticResource ModernTextbox}"/>
<TextBox Name="proteinTextBox" ToolTip="Fehérje (g)" Width="250" Height="40" VerticalContentAlignment="Center" HorizontalAlignment="Center" Margin="5" Style="{StaticResource ModernTextbox}"/>
<TextBox Name="fatTextBox" ToolTip="Zsír (g)" Width="250" Height="40" VerticalContentAlignment="Center" HorizontalAlignment="Center" Margin="5" Style="{StaticResource ModernTextbox}"/>
<Button Content="További értékek" Foreground="#353340" FontSize="14" Margin="0,10,60,0">
<Button.Style>
<Style TargetType="Button">
<Setter Property="TextElement.FontFamily" Value="Fonts/#Poppins"/>
<Setter Property="Background" Value="DarkGray"/>
<Setter Property="Cursor" Value="Hand"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#353340"/>
<Setter Property="Foreground" Value="DarkGray"/>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
<Button.Template>
<ControlTemplate TargetType="Button">
<Border Width="130" Height="25" CornerRadius="12" Background="{TemplateBinding Background}">
<ContentPresenter VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Border>
</ControlTemplate>
</Button.Template>
</Button>
<Button Content="Hozzáadás" Foreground="#353340" FontSize="14" Margin="500,50,0,0" Command="{Binding AddIngredientCommand}">
<Button.Style>
<Style TargetType="Button">
<Setter Property="TextElement.FontFamily" Value="Fonts/#Poppins"/>
<Setter Property="Background" Value="DarkGray"/>
<Setter Property="Cursor" Value="Hand"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#353340"/>
<Setter Property="Foreground" Value="DarkGray"/>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
<Button.Template>
<ControlTemplate TargetType="Button">
<Border Width="100" Height="25" CornerRadius="12" Background="{TemplateBinding Background}">
<ContentPresenter VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Border>
</ControlTemplate>
</Button.Template>
<Button.CommandParameter>
<MultiBinding Converter="{StaticResource IngredientConverter}" UpdateSourceTrigger="PropertyChanged">
<Binding Path="Text" ElementName="nameTextBox"/>
<Binding Path="Text" ElementName="weightTextBox"/>
<Binding Path="Text" ElementName="energyTextBox"/>
<Binding Path="Text" ElementName="carbohydrateTextBox"/>
<Binding Path="Text" ElementName="proteinTextBox"/>
<Binding Path="Text" ElementName="fatTextBox"/>
</MultiBinding>
</Button.CommandParameter>
</Button>
</StackPanel>
IngredientConverter.cs:
public class IngredientConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return String.Concat(values[0], ",", values[1], ",", values[2], ",", values[3], ",", values[4], ",", values[5]);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
return (value as string).Split(',');
}
}
AddIngredientViewModel.cs:
public class AddIngredientViewModel : ViewModelBase
{
#region Fields
private YazioRecipesAddOnModel _model;
#endregion
#region Properties
public DelegateCommand AddIngredientCommand { get; private set; }
#endregion
#region Constructor
public AddIngredientViewModel(YazioRecipesAddOnModel model)
{
_model = model;
AddIngredientCommand = new DelegateCommand(param => AddIngredient(param.ToString()));
}
#endregion
#region Public methods
public void AddIngredient(String ingredientString)
{
if (ingredientString == null)
return;
_model.AddIngredient(ingredientString);
}
#endregion
}
You should bind each TextBox to a different source property of the view model e.g.:
<TextBox Name="weightTextBox" Text="{Binding Weight}" />
You could then simply access the values directly in your AddIngredient method:
string ingredientString = string.Concat(this.Weight, "," ...);
_model.AddIngredient(ingredientString);
One of the key aspects of MVVM is to implement the application logic in the view models. Don't use converters for this.

Creating an Expand All and Collapse All Buttons with Expander in WPF

I am working in Visual Studio 2013 in WPF (C#) and I have the following code to create an expander from my xml. It is currently working perfectly right now but I want to include an expand all and collapse all buttons. I have looked all over and can't seem to find a solution.
Here is where the expander is created. I know I just have to iterate through a list of items and change the Property="IsExpanded" to Value="True" to create an expand all.
<DataTemplate x:Key="dtListTemplate" >
<StackPanel>
<Expander LostFocus="CollapseExpander" ExpandDirection="Down" Width="Auto">
<Expander.Style>
<Style TargetType="Expander">
<Setter Property="IsExpanded" Value="False" />
<Setter Property="Header" Value="{Binding XPath=#Name}" />
<Setter Property="FontWeight" Value="Bold"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsExpanded,RelativeSource={RelativeSource Self}}" Value="True">
</DataTrigger>
</Style.Triggers>
</Style>
</Expander.Style>
<ListBox Name="itemsList"
ItemsSource="{Binding XPath=UpgradeAction}"
ItemTemplate="{StaticResource dtListItemTemplate}"
SelectionChanged="listItems_SelectionChanged"
Style="{StaticResource styleListBoxUpgradeAction}"
ItemContainerStyle="{StaticResource styleListBoxItemUpgradeAction}">
</ListBox>
</Expander>
</StackPanel>
</DataTemplate>
Here's the code that calls the DataTemplate which has information from an Xml.
<StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<Border Grid.Column="0" Grid.Row="0" Width="790" Height="40" Padding="5" Background="#4E87D4">
<Label VerticalAlignment="Center" FontSize="16" FontWeight="Bold" Foreground="White">Test</Label>
</Border>
<Button Name="ExpandBttn" Width="100" Height="40" FontSize="16" FontWeight="Bold" Content="Expand All" DataContext="{Binding}" Click="Expand_Button_Click"/>
<Button Name="ColapseBttn" Width="100" Height="40" FontSize="16" FontWeight="Bold" Content="Colapse All" DataContext="{Binding}" Click="Collapse_Button_Click"/>
</StackPanel>
<ListView Name="listItems" Grid.Column="0" Grid.Row="1" Background="Wheat"
ItemsSource="{Binding Source={StaticResource xmldpUpgradeActions}, XPath=ActionGroup}"
ItemTemplate="{StaticResource dtListTemplateRichards}"
SelectionChanged="listItems_SelectionChanged">
</ListView>
</StackPanel>
Here's what I tried in the .cs file for the expand all portion.
private void Expand_Button_Click(object sender, RoutedEventArgs e)
{
foreach(var item in listItems.Items)
{
var listBoxItem = listItems.ItemContainerGenerator.ContainerFromItem(item) as ListBoxItem;
var itemExpander = (Expander)GetExpander(listBoxItem);
if (itemExpander != null)
itemExpander.IsExpanded = true;
}
}
private static DependencyObject GetExpander(DependencyObject container)
{
if (container is Expander) return container;
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(container); i++)
{
var child = VisualTreeHelper.GetChild(container, i);
var result = GetExpander(child);
if (result != null)
{
return result;
}
}
return null;
}
Any help is greatly appreciated!
Is xmldpUpgradeActions a CollectionViewSource?
Whatever class is in the collection, it should implement INotifyPropertyChanged.
Give it an IsExpanded property that raises PropertyChanged in its setter when its value changes, and bind that to Expander.IsExpanded in the template:
<Expander
IsExpanded="{Binding IsExpanded}"
LostFocus="CollapseExpander"
ExpandDirection="Down"
Width="Auto">
Write a command that loops through all the items in the collection and sets item.IsExpanded = false; on each one.

How do I bind text of checkbox to Header of Expander

I have an Expander that contains 3 checkbox.
<Expander Header="Bind Here" Grid.Row="3" Grid.ColumnSpan="2">
<StackPanel >
<CheckBox>Test 1</CheckBox>
<CheckBox>Test 2</CheckBox>
<CheckBox>Test 3</CheckBox>
</StackPanel>
</Expander>
I want to append the text of a checked checkbox to the Header of the expander.
Is it possible using binding?
Thanks for the answers but here's what I did:
<Expander Header="{Binding SelectedTitle}" Grid.Row="3" Grid.ColumnSpan="2">
<StackPanel >
<CheckBox IsChecked="{Binding Path=SelectedItem.isTest1, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">Test 1</CheckBox>
<CheckBox IsChecked="{Binding Path=SelectedItem.isTest2, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">Test 2</CheckBox>
<CheckBox IsChecked="{Binding Path=SelectedItem.isTest3, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">Test 3</CheckBox>
</StackPanel>
public String SelectedTitle
{
get
{
StringBuilder builder = new StringBuilder();
if (SelectedItem.isTest1)
builder.Append("Browse subfolders, ");
if (SelectedItem.isTest2)
builder.Append("Enter filename at the panel, ");
if (SelectedItem.isTest3)
builder.Append("Always show scan settings, ");
if (builder.Length == 0)
builder.Append("None");
return builder.ToString().Trim().TrimEnd(',');
}
}
private void SelectedItem_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
UpdateProperty("SelectedTitle");
}
When your checkbox is checked you need to track if it has been checked or not, you can do this either by binding or event handling or triggers.
Following is a quick example
Using Binding (XAML Only)
if you are keen to have it as binding you can try having triggers and setters on control template for expander like this (I leave it to user to edit and produce a minimal xaml the following piece works but may not be minimal) --
<Window.Resources>
<ControlTemplate x:Key="ControlTemplate" TargetType="Expander">
<Expander x:Name="MyExpander">
<StackPanel Orientation="Vertical">
<CheckBox x:Name="Box1">Test 1</CheckBox>
<CheckBox x:Name="Box2">Test 2</CheckBox>
<CheckBox x:Name="Box3"> Test 3</CheckBox>
</StackPanel>
</Expander>
<ControlTemplate.Triggers>
<Trigger SourceName="Box1" Property="CheckBox.IsChecked" Value="True">
<Setter Property="Header"
TargetName="MyExpander"
Value="{Binding Content, ElementName=Box1 }" />
</Trigger>
<Trigger SourceName="Box2"
Property="CheckBox.IsChecked"
Value="True">
<Setter Property="Header" TargetName="MyExpander"
Value="{Binding Content, ElementName=Box2 }" />
</Trigger>
<Trigger SourceName="Box3"
Property="CheckBox.IsChecked"
Value="True">
<Setter Property="Header"
TargetName="MyExpander"
Value="{Binding Content, ElementName=Box3 }" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Window.Resources>
<Expander Template="{StaticResource ControlTemplate}"/>
or if you don't mind code behind
Code Behind Way
private void ToggleButton_OnChecked(object sender, RoutedEventArgs e)
{
var checkBox = ((CheckBox) sender);
if (checkBox.IsChecked != null && checkBox.IsChecked.Value)
{
MyExpander.Header = checkBox.Content.ToString();
}
}
Xaml --
<Expander x:Name="MyExpander">
<StackPanel>
<CheckBox Checked="ToggleButton_OnChecked">Test 1</CheckBox>
<CheckBox Checked="ToggleButton_OnChecked">Test 2</CheckBox>
<CheckBox Checked="ToggleButton_OnChecked">Test 3</CheckBox>
</StackPanel>
</Expander>
Here is a cool way to do so:(Binding + Converter)
XAML:
<Expander Grid.Row="3" Grid.ColumnSpan="2" Name="expander" Width="500">
<Expander.Resources>
<local:ExpanderHeaderConverter x:Key="ExpanderHeaderConverter" />
</Expander.Resources>
<Expander.Header>
<TextBlock >
<TextBlock.Text>
<MultiBinding Converter="{StaticResource ExpanderHeaderConverter}">
<Binding ElementName="c1" Mode="OneWay"/>
<Binding ElementName="c1" Mode="OneWay" Path="IsChecked"/>
<Binding ElementName="c2" Mode="OneWay"/>
<Binding ElementName="c2" Mode="OneWay" Path="IsChecked"/>
<Binding ElementName="c3" Mode="OneWay"/>
<Binding ElementName="c3" Mode="OneWay" Path="IsChecked"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Expander.Header>
<StackPanel>
<CheckBox Name="c1" >Test 1</CheckBox>
<CheckBox Name="c2">Test 2</CheckBox>
<CheckBox Name="c3">Test 3</CheckBox>
</StackPanel>
</Expander>
Converter:
public class ExpanderHeaderConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string output = string.Empty;
for (int i = 0; i < values.Length; i+=2)
{
if ((values[i + 1] as bool?) == true)
output += (values[i] as CheckBox).Content.ToString()+" ";
}
return output;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}

Validation in DataGrid not showing Tooltip

In my xaml I am trying to display errors in Tooltip in a datagrid. The Texblock gets its border red and the grid row shows red "!" to say there is an error but tooptip is not displayed (on hovering the mouse)
xaml is
<Window.Resources>
<!--Error Template to change the default behaviour-->
<ControlTemplate x:Key="ErrorTemplate">
<DockPanel LastChildFill="True">
<Border BorderBrush="Red" BorderThickness="1">
<AdornedElementPlaceholder />
</Border>
</DockPanel>
</ControlTemplate>
<!--To display tooltip with the error-->
<Style TargetType="TextBlock">
<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>
</Window.Resources>
<DataGrid Name="grid" HorizontalAlignment="Stretch" ItemsSource="{Binding mMngModelList}" Margin="0,0,0,50" VerticalAlignment="Stretch" AutoGenerateColumns="False" CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Name">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{Binding Name}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Type">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Type}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{Binding Type}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Range Left">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding RangeLeft,ValidatesOnDataErrors=True, NotifyOnValidationError=True, ValidatesOnExceptions=True}" Validation.ErrorTemplate="{StaticResource ErrorTemplate}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{Binding RangeLeft, ValidatesOnDataErrors=True, NotifyOnValidationError=True, ValidatesOnExceptions=True}" Validation.ErrorTemplate="{StaticResource ErrorTemplate}"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
In Code my class implements IDataErrorInfo and the code behind is
public string this[string columnName]
{
get
{
var result = string.Empty;
switch (columnName)
{
case "RangeLeft":
if (RangeLeft == 0)
{
result = "RangeLeft should be greater than zero";
}
break;
}
return result;
}
}
public string Error
{
get
{
StringBuilder error = new StringBuilder();
// iterate over all of the properties
// of this object - aggregating any validation errors
PropertyDescriptorCollection props = TypeDescriptor.GetProperties(this);
foreach (PropertyDescriptor prop in props)
{
String propertyError = this[prop.Name];
if (propertyError != string.Empty)
{
error.Append((error.Length != 0 ? ", " : "") + propertyError);
}
}
return error.Length == 0 ? null : error.ToString();
}
}
Also is there a way that until all validations are satisfied, the ObservableCollection is not updated?
Try this Style :
<Style TargetType="TextBox">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>

Custom wpf checkbox click event not firing

Sure Im missing something simple but I have overridden a checkbox as a user control so I can use different images as its bullet decorators. Seems to work ok but doesnt respond to any click or checked events. I need it to display the bound popup upon the click evnt but not change the bullet decorator images.
Also my binding seems to be broken as when the click even fires (once it works) it should present a popup with the bound data. WarningList is my data to bind and when debugging it shows its populated correctly, just not bound correctly I believe
<UserControl x:Class="Dev.App.Views.Controls.StatusResultCheckBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:converters="clr-namespace:Dev.App.Views.Converters"
x:Name="Root"
d:DesignHeight="50"
d:DesignWidth="50"
mc:Ignorable="d">
<UserControl.Resources>
<converters:WarningListFilterByOperationTypeConverter x:Key="WarningListFilterByOperationTypeConverter" />
<DataTemplate x:Key="StatusResultNone">
<Viewbox Width="20" Height="20">
<Grid/>
</Viewbox>
</DataTemplate>
<DataTemplate x:Key="StatusResultWarning">
<BulletDecorator Background="Transparent">
<BulletDecorator.Bullet>
<Viewbox Width="20" Height="20">
<Grid>
<Path Data="F1M874.094,289.369L854.3,254.63C854.028,254.151 853.515,253.856 852.958,253.856 852.403,253.856 851.89,254.151 851.617,254.63L831.824,289.369C831.555,289.84 831.559,290.416 831.835,290.883 832.111,291.348 832.618,291.634 833.165,291.634L872.752,291.634C873.299,291.634 873.805,291.348 874.081,290.883 874.357,290.416 874.361,289.84 874.094,289.369z"
Stretch="Uniform" Fill="#FFFCCE00" Width="20" Height="20" Margin="0,0,0,0" RenderTransformOrigin="0.5,0.5" StrokeThickness="0.5">
</Path>
<Path Data="M855.653,287.189L850.264,287.189 850.264,282.745 855.653,282.745 855.653,287.189z M855.653,279.41L850.264,279.41 850.264,266.077 855.653,266.077 855.653,279.41z"
Stretch="Uniform" Fill="Black" Width="3" Height="10" Margin="0.5,3,0,0" RenderTransformOrigin="0.5,0.5" StrokeThickness="0.5">
</Path>
</Grid>
</Viewbox>
</BulletDecorator.Bullet>
<Popup Name="WarningMessagePopup"
Width="{Binding ElementName=ListBox, Path=ActualWidth}"
Height="200"
HorizontalAlignment="Center"
HorizontalOffset="50"
PlacementTarget="{Binding RelativeSource={RelativeSource TemplatedParent}}"
IsOpen="{Binding Path=IsChecked}"
StaysOpen="False"
VerticalOffset="10">
<TextBox VerticalScrollBarVisibility="Auto">
<TextBox.Text>
<MultiBinding Converter="{StaticResource WarningListFilterByOperationTypeConverter}" Mode="OneWay">
<Binding Path="OperationType" />
<Binding Path="DataContext.WarningList" RelativeSource="{RelativeSource AncestorType=ListBox}" />
</MultiBinding>
</TextBox.Text>
</TextBox>
</Popup>
</BulletDecorator>
</DataTemplate>
<ControlTemplate x:Key="CheckBoxTemplate" TargetType="ContentControl">
<ContentControl Name="Content" />
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding StatusResultCheckMode, ElementName=Root}" Value="None">
<Setter TargetName="Content" Property="ContentTemplate" Value="{StaticResource StatusResultNone}" />
</DataTrigger>
<DataTrigger Binding="{Binding StatusResultCheckMode, ElementName=Root}" Value="Warning">
<Setter TargetName="Content" Property="ContentTemplate" Value="{StaticResource StatusResultWarning}" />
<Setter Property="Cursor" Value="Hand" />
</DataTrigger>
<DataTrigger Binding="{Binding HasStatus, ElementName=Root}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<Style x:Key="StatusResultVisibilityStyle" TargetType="CheckBox">
<Setter Property="OverridesDefaultStyle" Value="true" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CheckBox}">
<BulletDecorator Background="Transparent">
<BulletDecorator.Bullet>
<Viewbox Width="20" Height="20">
<Grid />
</Viewbox>
</BulletDecorator.Bullet>
</BulletDecorator>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsInStatusNone.Value}" Value="False" />
<Condition Binding="{Binding IsInStatusWarning.Value}" Value="True" />
</MultiDataTrigger.Conditions>
<Setter Property="Visibility" Value="Visible" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<CheckBox HorizontalAlignment="Center"
VerticalAlignment="Center"
IsChecked="True"
Style="{StaticResource StatusResultVisibilityStyle}"/>
<ContentControl Grid.Column="0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Width="20" Height="20"
Template="{StaticResource CheckBoxTemplate}" />
</Grid>
</UserControl>
Called from my main page as follows. (nested inside a Listbox)
<controls:StatusResultCheckBox Grid.Column="3"
IsInStatusWarning="{Binding Path=IsInStatusWarning.Value}"
IsInStatusNone="{Binding Path=IsInStatusNone.Value}"
HasStatus="{Binding Path=HasStatus.Value}"
IsRunning="{Binding RelativeSource={RelativeSource AncestorType=ListBox}, Path=DataContext.IsRunning.Value}"/>
The code behind the user control...
using System.Windows;
using System.Windows.Controls;
namespace Dev.App.Views.Controls
{
/// <summary>
/// Interaction logic for StatusResultCheckBox.xaml
/// </summary>
public partial class StatusResultCheckBox : UserControl
{
public static readonly DependencyProperty StatusResultCheckModeProperty =
DependencyProperty.Register("StatusResultCheckMode", typeof(StatusResultCheckMode), typeof(StatusResultCheckBox), new PropertyMetadata(StatusResultCheckMode.None));
public static readonly DependencyProperty IsRunningProperty =
DependencyProperty.Register("IsRunning", typeof(bool), typeof(StatusResultCheckBox), new PropertyMetadata(false, IsRunningPropertyChanged));
public static readonly DependencyProperty HasStatusProperty =
DependencyProperty.Register("HasStatus", typeof(bool), typeof(StatusResultCheckBox), new PropertyMetadata(false, IsRunningPropertyChanged));
public static readonly DependencyProperty IsInStatusWarningProperty =
DependencyProperty.Register("IsInStatusWarning", typeof(bool), typeof(StatusResultCheckBox), new PropertyMetadata(false, IsRunningPropertyChanged));
public static readonly DependencyProperty IsInStatusNoneProperty =
DependencyProperty.Register("IsInStatusNone", typeof(bool), typeof(StatusResultCheckBox), new PropertyMetadata(false, IsRunningPropertyChanged));
private static void IsRunningPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var statusResultCheckBox = d as StatusResultCheckBox;
if (statusResultCheckBox != null)
{
if (statusResultCheckBox.IsRunning)
{
if (statusResultCheckBox.IsInStatusWarning)
{
statusResultCheckBox.StatusResultCheckMode = StatusResultCheckMode.Warning;
statusResultCheckBox.HasStatus = true;
}
else
{
statusResultCheckBox.StatusResultCheckMode = StatusResultCheckMode.None;
statusResultCheckBox.HasStatus = false;
}
}
}
}
public StatusResultCheckBox()
{
InitializeComponent();
}
public StatusResultCheckMode StatusResultCheckMode
{
get { return (StatusResultCheckMode)GetValue(StatusResultCheckModeProperty); }
set { SetValue(StatusResultCheckModeProperty, value); }
}
public bool IsRunning
{
get { return (bool)GetValue(IsRunningProperty); }
set { SetValue(IsRunningProperty, value); }
}
public bool HasStatus
{
get { return (bool)GetValue(HasStatusProperty); }
set { SetValue(HasStatusProperty, value); }
}
public bool IsInStatusWarning
{
get { return (bool)GetValue(IsInStatusWarningProperty); }
set { SetValue(IsInStatusWarningProperty, value); }
}
public bool IsInStatusNone
{
get { return (bool)GetValue(IsInStatusNoneProperty); }
set { SetValue(IsInStatusNoneProperty, value); }
}
}
public enum StatusResultCheckMode
{
None,
Warning
}
}
If you want to do something like this, it probably will be easier if you just apply a style to a existing checkbox.
You can get the default style from here and change what you need.
Ended up refactoring all this code and simplifying. Basically styled a togglebutton.
<ToggleButton Grid.Column="3" Style="{StaticResource WarningStyle}"/>
And in the resource file...
<Style x:Key="WarningStyle" TargetType="ToggleButton">
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="Cursor" Value="Hand" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="Visibility" Value="Collapsed" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Grid>
<Path Data="F1M874.094,289.369L854.3,254.63C854.028,254.151 853.515,253.856 852.958,253.856 852.403,253.856 851.89,254.151 851.617,254.63L831.824,289.369C831.555,289.84 831.559,290.416 831.835,290.883 832.111,291.348 832.618,291.634 833.165,291.634L872.752,291.634C873.299,291.634 873.805,291.348 874.081,290.883 874.357,290.416 874.361,289.84 874.094,289.369z"
Stretch="Uniform" Fill="#FFFCCE00" Width="20" Height="20" Margin="0,0,0,0" RenderTransformOrigin="0.5,0.5" StrokeThickness="0.5">
</Path>
<Path Data="M855.653,287.189L850.264,287.189 850.264,282.745 855.653,282.745 855.653,287.189z M855.653,279.41L850.264,279.41 850.264,266.077 855.653,266.077 855.653,279.41z"
Stretch="Uniform" Fill="Black" Width="3" Height="10" Margin="0.5,3,0,0" RenderTransformOrigin="0.5,0.5" StrokeThickness="0.5">
</Path>
<Popup Name="WarningMessagePopup"
Width="425"
Height="150"
HorizontalAlignment="Center"
HorizontalOffset="22"
PlacementTarget="{Binding RelativeSource={RelativeSource TemplatedParent}}"
IsOpen="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsChecked}"
StaysOpen="False"
VerticalOffset="7">
<ListBox ItemsSource="{Binding Path=WarningList}">
<ListBox.Style>
<Style TargetType="{x:Type ListBox}">
<Style.Triggers>
<Trigger Property="IsVisible" Value="True">
<Setter Property="Background" Value="#F8FCFF"/>
</Trigger>
</Style.Triggers>
</Style>
</ListBox.Style>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource WarningListFilterByOperationTypeConverter}" Mode="OneWay">
<Binding Path="Count" />
<Binding Path="Message" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Popup>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding IsInStatusWarning.Value}" Value="True" >
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>

Categories