This is the first time I have to ask in this forum.
I am developing my first WPF application that takes data from ASYNC methods to display whith a spinner while it is working.
I had no problems doing one (called CheckServices() that fills data in Labels, TextBlocks....), but I had an error doing a second method (called FillDataGrid(() that fills data in a DataGrid).
I come from web applications and I am not sure if I am doing it like in Ajax calls that I usually do.
My MainView:
public MainViewController()
{
InitializeComponent();
InitializeElements();
FillDataGrid(() =>
{
return new ItemService().ReadItemsByUser(SessionData.UserCertificate.User.DNI);
});
CheckServices();
Title = SessionData.AppData.AppName + " v" + SessionData.AppData.AppVersion;
Show();
}
This is the Method that works well:
async private void CheckServices()
{
//Show spinner
icoWorking.Visibility = Visibility.Visible;
List<ServiceModel> services =//From other method
Task<List<ServiceModel>> task = Task.Run(()=> {
foreach (ServiceModel ser in services)
{
//A proccess
}
return services;
});
services = await task;
// A process to asign data to view elements
//Hide spinner
icoWorking.Visibility = Visibility.Hidden;
}
This is the method that brokes the app:
async private void FillDataGrid(Func<List<ItemRepositoryDTO>> p)
{
List<RepositoryModel> list = null;
//Show spinner
icoWorking.Visibility = Visibility.Visible;
List<ItemRepositoryDTO> items = p();
if (items != null)
{
Task<List<RepositoryModel>> task = Task.Run(() =>
{
List<RepositoryModel> listAux = new List<RepositoryModel>();
//A proccess with "items"
return listAux;
});
list = await task;
//Problematic sentence ****
dgRepositoryItems.ItemsSource = list;
//*****
}
//Hide spinner
icoWorking.Visibility = Visibility.Hidden;
}
If I comment "the problematic sentence", it works well, else the error comes when the method ends (not when items are asigned to the DataGrid). If I dont use async methods it works well but the spinner do not show.
Tried Task.WaitAll(t)/Task.WhenAll(t) + t.Result in MainWindow but that method always break the app without going througth "catch".
The XAML looks like this:
<DataGrid CanUserAddRows="False" CanUserDeleteRows="False" CanUserReorderColumns="False" CanUserSortColumns="False" ItemsSource="{Binding RepositoryModel}" x:Name="dgRepositoryItems" AutoGenerateColumns="False" BorderBrush="{x:Null}" Margin="0,60,0,41" Background="#FFECECEC">
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="Background" Value="#FF50B262"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
</Style>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn Header="Nombre" IsReadOnly="True" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Margin" Value="5,0,0,0"/>
</Style>
</StackPanel.Resources>
<fa:ImageAwesome Visibility="{Binding ShowRootPath}" VerticalAlignment="Bottom" Height="14" Icon="ArrowCircleOutlineRight" Foreground="Black" />
<TextBlock Text="{Binding GridTab}" />
<fa:ImageAwesome VerticalAlignment="Bottom" Height="14" Icon="{Binding IconPath}" Foreground="Gray" />
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn IsReadOnly="True" Width="*" Header="Repositorio" Binding="{Binding Repository}" />
<DataGridTemplateColumn Header="Estado" IsReadOnly="True" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Margin" Value="5,0,0,0"/>
</Style>
</StackPanel.Resources>
<fa:ImageAwesome VerticalAlignment="Bottom" Height="14" Icon="{Binding IconState}" Foreground="{Binding IconStateColor}" />
<TextBlock Text="{Binding State}" />
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Can someone tell me what I am doing wrong and a simple example that how should it be to do the same?
Thanks in advance
UPDATE
The problem seems to be other different thing:
In the Task that prepares data to be inserted in the DataGrid, I was filling a field with class SolidColorBrush.
It seems cannot be created in other thread than in the UI one.
Solved using Brush instead SolidColorBrush.
I found the solution here
Blockquote
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.
I'm developing Universal Window App on Windows 10 platform and my page looks like this example :
Main page contain a pivot control with 4 pivot items (PAGE 1, PAGE 2, ...).
PAGE 1 contain a red stackpanel which contain a listview with horizontal scrolling.
My problem is, when i want to scroll my red listview horitontaly, my pivot swipe to next page.
I want to disable pivot swipe during listview scrolling (But only during listview scrolling).
I tried to get scrollviewer from listview and to listen viewChange from scrollviewer to disable pivot swipe but without success.
Everything work but set IsHitTestVisible to false seems to not work.
Here is my code :
ScrollViewer scrollViewer = ListViewHelper.GetScrollViewer(myListView);
scrollViewer.ViewChanged += scrollview_ViewChanged;
private void scrollview_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
{
var scrollviewer = sender as ScrollViewer;
if (e.IsIntermediate)
{
mainPivot.IsHitTestVisible = false;
} else
{
mainPivot.IsHitTestVisible = true;
}
}
And my ListViewHelper class :
public static class ListViewHelper
{
public static ScrollViewer GetScrollViewer(this DependencyObject element)
{
if (element is ScrollViewer)
{
return (ScrollViewer)element;
}
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++)
{
var child = VisualTreeHelper.GetChild(element, i);
var result = GetScrollViewer(child);
if (result == null)
{
continue;
}
else
{
return result;
}
}
return null;
}
}
And my xaml code :
<Pivot x:Name="mainPivot">
<PivotItem x:Name="pivot1">
<!-- Header -->
<PivotItem.Header>
<controls:TabHeader x:Uid="pivot1HeaderTitle"
Label=""
Glyph=""/>
</PivotItem.Header>
<!-- Content -->
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="110" />
<RowDefinition Height="30" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel x:Name="localeEditionsFavory"
Grid.Row="0">
[...]
<!-- HERE is my listview -->
<ListView x:Name="localeEditionsFavoryList"
Height="80"
ItemsSource="{Binding FavoritesItems}"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollMode="Enabled">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal">
</StackPanel>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Padding" Value="0,0,0,0" />
<Setter Property="Margin" Value="10" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Grid x:Name="favoryList">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="90" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="55" />
</Grid.RowDefinitions>
<Rectangle x:Name="strokedasharray"
Grid.Column="0"
Grid.Row="0"
Fill="White"
Opacity="0.2"
RadiusX="5"
RadiusY="5"/>
<TextBlock Grid.Column="0"
Grid.Row="0"
Text="{Binding FavoriteItem.EditionKey}"
TextWrapping="WrapWholeWords"
Height="auto"
Foreground="White"
FontSize="14"
Margin="2"
HorizontalAlignment="Center"
VerticalAlignment="Center"
TextTrimming="CharacterEllipsis"
TextAlignment="Center"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
[...]
<ScrollViewer VerticalScrollMode="Auto"
VerticalScrollBarVisibility="Auto"
Grid.Row="3">
<controls:NewsItemControl x:Name="NewsListControl"
Visibility="{Binding Busy, Converter={StaticResource InverseBoolToVisibilityConverter}}"/>
</ScrollViewer>
</Grid>
</PivotItem>
[...]
Does any one has an idea to resolve this problem ?
The undesired behavior you're seeing is a result of scroll chaining. Both the ListView and Pivot contain ScrollViewers in their control templates making this a nested ScrollViewer situation. When you have a ScrollViewer contained within the tree of another and attempt to scroll via touch beyond the extent of the inner ScrollViewer then the outer ScrollViewer engages and begins scrolling.
With chaining disabled you’d only be able to scroll the outer ScrollViewer by beginning the manipulation on a hit-testable area of the outer ScrollViewer. Chaining is enabled by default on the ScrollViewer so when you attempt to scroll past the end of the list the chaining kicks in and is recognized by the Pivot as an attempt to “scroll” to the next pivot item.
Disable chaining on the ListView's ScrollViewer (and remove the ListViewHelper stuff in code behind) by setting the ScrollViewer.IsHorizontalScrollChainingEnabled="False" attached property:
<!-- HERE is my listview -->
<ListView x:Name="localeEditionsFavoryList"
Height="80"
ItemsSource="{Binding FavoritesItems}"
ScrollViewer.IsHorizontalScrollChainingEnabled="False"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollMode="Enabled">
If you need to make the ListView horizontal scrolling, you can achieve this just in XAML code by modifying the style of the ListView like this:
<Style x:Key="ListViewStyle" TargetType="ListView">
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Hidden" />
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Disabled" />
<Setter Property="ScrollViewer.HorizontalScrollMode" Value="Enabled" />
<Setter Property="ScrollViewer.IsHorizontalRailEnabled" Value="True" />
<Setter Property="ScrollViewer.VerticalScrollMode" Value="Disabled" />
<Setter Property="ScrollViewer.IsVerticalRailEnabled" Value="False" />
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<ItemsStackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
Now if you disable the scrolling of Pivot when you scroll the ListView of "PAGE 1", you can edit your code like this:
public void Page_Loaded(object sender, RoutedEventArgs e)
{
var scrollViewer = FindChildOfType<ScrollViewer>(mainPivot);
scrollViewer.HorizontalScrollMode = ScrollMode.Disabled;
}
private void PivotSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (mainPivot.SelectedIndex == 0)
{
var scrollViewer = FindChildOfType<ScrollViewer>(mainPivot);
scrollViewer.HorizontalScrollMode = ScrollMode.Disabled;
}
}
public static T FindChildOfType<T>(DependencyObject root) where T : class
{
var queue = new Queue<DependencyObject>();
queue.Enqueue(root);
while (queue.Count > 0)
{
DependencyObject current = queue.Dequeue();
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(current); i++)
{
var child = VisualTreeHelper.GetChild(current, i);
var typedChild = child as T;
if (typedChild != null)
{
return typedChild;
}
queue.Enqueue(child);
}
}
return null;
}
This is my first question here on SO...I have been a ready for a long time and never needed to ask for help because I usually find what I need, but I am having a hard time with this one...
I am working on a tools suite in WPF. I created a few User Controls as follow:
LabelTextBox (Label on the left and TextBox on the right)
LabelTextBoxToggle (LabelTextBox on the left and Checkbox on the right)
LabelTextBoxBrowseFile (LabelTextBox on the left and Browse File Button on the right)
I use Dependency Properties to bind all the properties I need and they all work fine. The problem I ran into recently is getting ValidationRules to work correctly on the base TextBox I use in LabelTextBox when those rules are applied to the LabelTextBoxToggle and LabelTextBoxBrowseFile UserControls, since I have to bind 2 levels down in order to update controls in LabelTextBox. I can get the Validation Rule to run, but I can't get the TextBox control to update its background color accordingly when errors are found, like I do when LabelTextBox isn't nested within another User Control.
So, here's my code below:
Style used for TextBox:
<!-- TextBox Default Style, Supports Validation Rules -->
<Style TargetType="{x:Type TextBox}">
<Setter Property="Background" Value="{StaticResource TextBoxBGDefault}" />
<Style.Triggers>
<Trigger Property="IsKeyboardFocused" Value="True">
<Setter Property="Background" Value="{StaticResource TextBoxBGHasFocus}" />
</Trigger>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Background" Value="{StaticResource TextBoxBGHasFocus}" />
</Trigger>
<DataTrigger Binding="{Binding Path=(Validation.HasError)}" Value="true">
<Setter Property="Background" Value="{StaticResource TextBoxBGHasError}" />
<Setter Property="BorderBrush" Value="Firebrick" />
<Setter Property="BorderThickness" Value="1.5" />
<Setter Property="ToolTipService.InitialShowDelay" Value="2" />
<Setter Property="ToolTip" Value="{Binding Path=(Validation.Errors)[0].ErrorContent}" />
</DataTrigger>
</Style.Triggers>
</Style>
LabelTextBox.xaml:
<Grid x:Name="LayoutRoot" DataContext="{Binding ElementName=ControlRoot, Mode=OneWay, ValidatesOnDataErrors=True}">
<Grid.RowDefinitions>
<RowDefinition Height="24" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Label
x:Name="NameLabel"
Width="{Binding Path=LabelWidth, Converter={StaticResource WidthToAutoConverter}}"
Margin="0"
HorizontalAlignment="{Binding Path=HorizontalContentAlignment}"
HorizontalContentAlignment="{Binding Path=LabelHAlign, Converter={StaticResource valueToStringConverter}}"
VerticalContentAlignment="Center"
Content="{Binding Path=LabelContent}"
Padding="10,2,5,2" />
<TextBox
x:Name="ValueTextBox"
Grid.Column="1"
KeyDown="TextBox_KeyDown_Enter"
Padding="5,0"
Text="{Binding TextBoxContent, Mode=TwoWay}"
TextChanged="TextBox_TextChanged" VerticalContentAlignment="Center" Height="22" VerticalAlignment="Center" />
<TextBlock
x:Name="ErrorMsgTextBlock"
Grid.Row="1"
Grid.Column="1"
Margin="0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Style="{DynamicResource ValidationErrorLabel}"
Text="{Binding Path=(Validation.Errors)[0].ErrorContent, ElementName=ControlRoot}"
Visibility="{Binding Path=(Validation.HasError), Converter={StaticResource BooleanToVisibilityConverter}, ElementName=ControlRoot, Mode=OneWay}" TextWrapping="Wrap" />
</Grid>
LabelTextBoxBaseClass:
#region TextBox Dependency Properties
public string TextBoxContent
{
get { return (string)GetValue( TextBoxContentProperty ); }
set { SetValue( TextBoxContentProperty, value ); }
}
public static readonly DependencyProperty TextBoxContentProperty =
DependencyProperty.Register( "TextBoxContent"
, typeof( string )
, typeof( LabelTextBoxBaseClass ), new PropertyMetadata( "" )
);
LabelTextBoxToggle.xaml:
<!-- This is the nested UserControl -->
<local:LabelTextBox
x:Name="LTBControl"
Margin="0"
VerticalContentAlignment="Center"
IsEnabled="{Binding Path=IsChecked, ElementName=ToggleCheckBox}"
LabelContent="{Binding Path=LabelContent}"
LabelHAlign="{Binding Path=LabelHAlign}"
LabelWidth="{Binding Path=LabelWidth}"
RaiseEnterKeyDownEvent="{Binding RaiseEnterKeyDownEvent, Mode=TwoWay}"
RaiseTextChangedEvent="{Binding RaiseTextChangedEvent, Mode=TwoWay}"
TextBoxContent="{Binding Path=TextBoxContent, Mode=TwoWay}" />
<CheckBox
x:Name="ToggleCheckBox"
Grid.Column="1"
Margin="5,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Click="ToggleCheckBox_Click"
IsChecked="{Binding CheckBoxChecked, Mode=TwoWay}" />
MaterialBuilder.xaml:
<UserControl.Resources>
<BindingGroup x:Key="SRBindingGroup" Name="PropertiesBindingGroup">
<BindingGroup.ValidationRules>
<local:AddMaterialRule ValidationStep="ConvertedProposedValue" />
</BindingGroup.ValidationRules>
</BindingGroup>
<srvalidators:StringNullOrEmptyValidationRule x:Key="stringNullOrEmptyValidationRule" ErrorMessage="Custom Dir cannot be null!" />
<srconverters:ListToStringConverter x:Key="ListToStringConverter" />
<srconverters:ListToStringConverter x:Key="listToStringConverter" />
<sys:String x:Key="newLine">\n</sys:String>
</UserControl.Resources>
<StackPanel x:Name="spSetup">
<!-- This contains a nested UserControl (LabelTextBox), and I can't get its TextBox background to change color, I just get the red border around the whole control on Validation Errors. -->
<srcontrols:LabelTextBoxBrowseFile
x:Name="ltbMaterialBlueprint"
Height="Auto"
Margin="0,5"
LabelContent="Material Blueprint:"
LabelWidth="120"
LostFocus="ltbMaterialBlueprint_UpdateUI"
OnButtonClick="ltbMaterialBlueprint_UpdateUI"
OnTextBoxEnterKeyDown="ltbMaterialBlueprint_UpdateUI"
TextBoxContent="{Binding MaterialBlueprintFilePath, Mode=TwoWay}">
<srcontrols:LabelTextBoxBrowseFile.TextBoxContent>
<Binding
Mode="TwoWay"
Path="CustomDirName"
UpdateSourceTrigger="PropertyChanged"
ValidatesOnDataErrors="True">
<Binding.ValidationRules>
<srvalidators:StringNullOrEmptyValidationRule ErrorMessage="Custom Dir cannot be empty!" />
</Binding.ValidationRules>
</Binding>
</srcontrols:LabelTextBoxBrowseFile.TextBoxContent>
</srcontrols:LabelTextBoxBrowseFile>
<!-- Here I use the base LabelTextBox control by itself and everything works as intended. The TextBox's background color changes to red on Validation Errors. -->
<srcontrols:LabelTextBox
x:Name="ltbMaterialName"
Margin="0,5,10,5"
LabelContent="Name:"
LabelWidth="60"
OnTextBoxTextChange="ltbMaterialName_Validate"
RaiseEnterKeyDownEvent="True"
RaiseTextChangedEvent="True">
<!-- Set-up the TextBox Content to use the ValidationRule by passing this GroupBox's BindingGroup resource as a parameter -->
<srcontrols:LabelTextBox.TextBoxContent>
<Binding
Mode="TwoWay"
Path="MaterialName"
UpdateSourceTrigger="Explicit"
ValidatesOnDataErrors="True">
<Binding.ValidationRules>
<local:AddMaterialRule
BGroup="{StaticResource SRBindingGroup}"
CheckForDuplicates="True"
CheckForEmptyName="True"
IsMaterialName="True"
ValidationStep="ConvertedProposedValue" />
</Binding.ValidationRules>
</Binding>
</srcontrols:LabelTextBox.TextBoxContent>
</srcontrols:LabelTextBox>
</StackPanel>
I know it's probably a DataContext issue, but unlike the other controls and dependency properties, I cannot figure out how to make the base UserControl ui elements update their look when Validation Errors are found. Here's some images of what I mean:
Working TextBox (LabelTextBox control used here):
Working TextBox Example
Broken TextBox (LabelTextBoxToggle control used here, with nested LabelTextBox):
Broken TextBox (nested in UserControl)
Any help or suggestion is very welcomed of course! Thanks for your time!
Your problem is similar to mine. I've also created custom control containing text block (as label) and text box (as input). The goal is to have universal control for data input with simple label. The problem was validation. I've also managed easily to bind and validate data, but displaying errors with template on specified textbox that was inside my control... that was the issue and if I understand correctly you have the same problem. So my solution is:
<UserControl x:Class="CapMachina.Common.Controls.FormField_UC" x:Name="FormFieldCtrl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:CapMachina.Common.Controls"
xmlns:Converters="clr-namespace:CapMachina.Common.Converters"
xmlns:metro="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<Converters:ConditionalValueConverter x:Key="conditionalValueConverter" />
<Converters:NullableObjectToVisibilityConverter x:Key="nullableObjectToVisibilityConverter" />
</UserControl.Resources>
<StackPanel>
<TextBlock FontWeight="Bold" Text="{Binding Header, ElementName=FormFieldCtrl}" Margin="1" />
<TextBox x:Name="MainTxtBx" metro:TextBoxHelper.Watermark="{Binding WaterMarkText, ElementName=FormFieldCtrl}" TextWrapping="Wrap"
Text="{Binding Text, ElementName=FormFieldCtrl, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, ValidatesOnExceptions=True}"
Margin="1" IsReadOnly="{Binding IsReadOnly, ElementName=FormFieldCtrl}" TextChanged="MainTxtBx_TextChanged" Loaded="MainTxtBx_Loaded">
<TextBox.Style>
<MultiBinding Converter="{StaticResource conditionalValueConverter}">
<Binding Path="IsReadOnly" ElementName="FormFieldCtrl" />
<Binding Path="ReadOnlyStyle" ElementName="FormFieldCtrl" />
<Binding Path="DefaultStyle" ElementName="FormFieldCtrl" />
</MultiBinding>
</TextBox.Style>
</TextBox>
</StackPanel>
</UserControl>
And code behind:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace CapMachina.Common.Controls
{
public partial class FormField_UC : UserControl
{
public string Header
{
get { return (string)GetValue(HeaderProperty); }
set { SetValue(HeaderProperty, value); }
}
public static readonly DependencyProperty HeaderProperty =
DependencyProperty.Register("Header", typeof(string), typeof(FormField_UC));
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(FormField_UC));
public string WaterMarkText
{
get { return (string)GetValue(WaterMarkTextProperty); }
set { SetValue(WaterMarkTextProperty, value); }
}
public static readonly DependencyProperty WaterMarkTextProperty =
DependencyProperty.Register("WaterMarkText", typeof(string), typeof(FormField_UC));
public bool IsReadOnly
{
get { return (bool)GetValue(IsReadOnlyProperty); }
set { SetValue(IsReadOnlyProperty, value); }
}
public static readonly DependencyProperty IsReadOnlyProperty =
DependencyProperty.Register("IsReadOnly", typeof(bool), typeof(FormField_UC), new PropertyMetadata(true));
public Style ReadOnlyStyle { get; set; }
public Style DefaultStyle { get; set; }
public FormField_UC()
{
ReadOnlyStyle = Application.Current.FindResource("ReadOnlyTextBox") as Style;
DefaultStyle = Application.Current.FindResource("DefaultTextBox") as Style;
InitializeComponent();
}
private void MainTxtBx_TextChanged(object sender, TextChangedEventArgs e)
{
if (string.IsNullOrEmpty(MainTxtBx.Text) && IsReadOnly)
Visibility = Visibility.Collapsed;
else
Visibility = Visibility.Visible;
}
private void MainTxtBx_Loaded(object sender, RoutedEventArgs e)
{
BindingExpression mainTxtBxBinding = BindingOperations.GetBindingExpression(MainTxtBx, TextBox.TextProperty);
BindingExpression textBinding = BindingOperations.GetBindingExpression(this, TextProperty);
if (textBinding != null && mainTxtBxBinding != null && textBinding.ParentBinding != null && textBinding.ParentBinding.ValidationRules.Count > 0 && mainTxtBxBinding.ParentBinding.ValidationRules.Count < 1)
{
foreach (ValidationRule vRule in textBinding.ParentBinding.ValidationRules)
mainTxtBxBinding.ParentBinding.ValidationRules.Add(vRule);
}
}
}
}
Usage:
<Controls:FormField_UC Header="First name" IsReadOnly="False" HorizontalAlignment="Left" VerticalAlignment="Top">
<Controls:FormField_UC.Text>
<Binding Path="Person.FirstName" Mode="TwoWay">
<Binding.ValidationRules>
<VDRules:NamesValidationRule InventoryPattern="{StaticResource NamesRegex}">
<VDRules:NamesValidationRule.Attributes>
<Validation:ValidationAttributes IsRequired="True" />
</VDRules:NamesValidationRule.Attributes>
</VDRules:NamesValidationRule>
</Binding.ValidationRules>
</Binding>
</Controls:FormField_UC.Text>
</Controls:FormField_UC>
What i did was copy validation rules to nested text box after all bindings were created. You cannot modify binding after use, but you can add validation rules to it :)
It is very important to set certain properties inside custom control like:
<UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, ValidatesOnExceptions=True>
cos you cannot set them afterwards. So setting them in usage line is not needed.
What I need is that when mouse per listviewitem show me all data from each in a tooltip.
This is a part of my viewmdel
...
...
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.CommandWpf;
...
...
private ObservableCollection<Articulo> _articulos;
private Articulo _articuloSeleccionado;
public ObservableCollection<Articulo> Articulos
{
get { return _articulos; }
set
{
_articulos = value;
RaisePropertyChanged();
}
}
public Articulo ArticuloSeleccionado
{
get { return _articuloSeleccionado; }
set
{
_articuloSeleccionado = value;
RaisePropertyChanged();
}
}
My .xalm
<ListView Name="lvResultado"
ItemsSource="{Binding Articulos}"
SelectedItem="{Binding ArticuloSeleccionado}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Center"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.View>
<GridView>
<GridViewColumn Header="Código de barras" Width="200" DisplayMemberBinding="{Binding CodigoDeBarras}"/>
<GridViewColumn Header="Descripción" Width="250" DisplayMemberBinding="{Binding Descripcion}"/>
</GridView>
</ListView.View>
</ListView>
Thank you for your help. I tried several things but no good result.
You can define an ItemContainerStyle that would set your tooltip template and content.
See an example below, here I define an UniformGrid to display multiple text lines in one column. You are free to setup your tooltip as you wish. You still need to tell the view which data properties need to be displayed in the tooltip.
<ListView ItemsSource="{Binding Articulos}">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="ToolTip">
<Setter.Value>
<UniformGrid Columns="1">
<TextBlock Text="{Binding CodigoDeBarras}"/>
<TextBlock Text="{Binding Descripcion}"/>
<TextBlock Text="{Binding AnyOtherProperty}"/>
</UniformGrid>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
</ListView>