Binding InputValidation additional data using DependencyProperty - c#

kind of missing here something: I'm using input Validation in a datatemplate (which works) and need to supply additional data to the validation (which doesn't work). Using "hard-coded" values (see comment in code below) works, using the binding doesn't.
The datatemplate is applied to elements (which have a "value", "low_lim" and "high_lim" properties) displayed in an listView (this also works). I think I just can't wrap my head around binding.
May be I just looked to long at this problem. If I'm right this is some kind of "visual-tree" problem with the DP...
Any help appreciated.
<DataTemplate DataType="{x:Type OptionA}" >
<TextBox Width="100" Validation.ErrorTemplate="{StaticResource validationErrorTemplate}">
<TextBox.Text>
<Binding Path="value" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<val:NumberOptionValidator>
<val:NumberOptionValidator.validRange>
<val:Range low_lim="{Binding low_lim}" high_lim="{Binding high_lim}"/>
<!--<val:Range low_lim="1" high_lim="2"/>-->
</val:NumberOptionValidator.validRange>
</val:NumberOptionValidator>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</DataTemplate>

I'd suggest you implement IDataErrorInfo on OptionA and not use the validation rule.
According to https://blogs.msdn.microsoft.com/wpfsdk/2007/10/02/data-validation-in-3-5/ in the "When to use Validation Rules vs. IDataErrorInfo" section, for "UI or business-layer validation logic":
Use Validation Rules:
The validation logic is detached from the data source, and can be reused between controls.
Use IDataErrorInfo: The validation logic is closer to the source.
Here is some code to get you started.
XAML:
<Window x:Class="WpfApplication33.MainWindow"
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:local="clr-namespace:WpfApplication33"
xmlns:val="clr-namespace:WpfApplication33"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:VM />
</Window.DataContext>
<Window.Resources>
<ControlTemplate x:Key="validationErrorTemplate">
<StackPanel Orientation="Horizontal">
<AdornedElementPlaceholder x:Name="textBox"/>
<TextBlock Text="{Binding [0].ErrorContent}" Foreground="Red"/>
</StackPanel>
</ControlTemplate>
<DataTemplate DataType="{x:Type local:OptionA}">
<TextBox Width="100"
Validation.ErrorTemplate="{StaticResource validationErrorTemplate}"
Text="{Binding value, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
</DataTemplate>
</Window.Resources>
<Grid>
<ListView ItemsSource="{Binding Items}" />
</Grid>
</Window>
CS:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
namespace WpfApplication33
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
public class VM
{
public List<OptionA> Items { get; set; }
public VM()
{
Items = new List<OptionA>() {
new OptionA() {value=1, low_lim = 0, high_lim = 2 },
new OptionA() {value=2, low_lim = 3, high_lim = 4 }, // too low
new OptionA() {value=3, low_lim = 2, high_lim = 5 },
new OptionA() {value=4, low_lim = 6, high_lim = 9 }, // too low
new OptionA() {value=5, low_lim = 0, high_lim = 4 }, // too high
};
}
}
public class OptionA : INotifyPropertyChanged, IDataErrorInfo
{
public event PropertyChangedEventHandler PropertyChanged;
private void OPC(string name) { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(name)); }
private int _value, _low_lim, _high_lim;
public int value { get { return _value; } set { if (_value != value) { _value = value; OPC("value"); } } }
public int low_lim { get { return _low_lim; } set { if (_low_lim != value) { _low_lim = value; OPC("low_lim"); } } }
public int high_lim { get { return _high_lim; } set { if (_high_lim != value) { _high_lim = value; OPC("high_lim"); } } }
#region IDataErrorInfo
public string Error
{
get
{
return null;
}
}
public string this[string columnName]
{
get
{
string err = null;
if (columnName == "value")
{
if (value < low_lim || value > high_lim)
err = string.Format("Value is out of the range of {0} to {1}.", low_lim, high_lim);
}
return err;
}
}
#endregion
}
}
Screenshot:

Related

How to get access to a binding to a WPF Binding on C# CodeBehind level

I am a VB.Net programmer and quite new to C#. I am at a point where I'm stuck.
I want to make an app to create a quotation with Word. This Quotation should consist of two Word files.
The Word files are Templates with Bookmarks, so writing to them should be no problem.
I want to have a WPF User Interface where the User can describe the Article and when clicking on a button the two Word files will be created.
I made the WPF User Interface and binded the Textboxes to a cl_Data.cs Class where are Properties such as : Description, FunctionName, etc.
My Problem:
How can i access the Data from the User Interface from my Code Behinde to shift it to the Word files?
The Code:
WPF: How i Bind it on .xaml level
<Window.Resources>
<!-- Binding the Data Class-->
<local:Cl_Data x:Key="Data"
Dealer="Test"
Customer="Tester"
Machine="M***s"
PRJ="123456"
DeliveryTime="6"
Description="Managing different chucks, Saving position data of the linear sensor for chuck clamp unclamp position"
Operation="The operator can select a chuck form the chuck management and save the clamp and unclamp position and reuse this position for next time"
FunctionName="GeneratorAPP"
Requirements="API-Kit"
/>
</Window.Resources>
How i call it on .xaml level (same document) -> This works
<Border BorderBrush="#FFB0F0FF" BorderThickness="1" Height="26">
<TextBox x:Name="Tb_Dealer"
TextWrapping="Wrap" Text="{Binding Dealer, UpdateSourceTrigger=PropertyChanged}" Width="auto" Foreground="#FFB0F0FF" BorderBrush="#00ABADB3" Background="Transparent" TextAlignment="Center" VerticalAlignment="Center" />
</Border>
<Border BorderBrush="#FFB0F0FF" BorderThickness="1" Height="26">
<TextBox x:Name="Tb_Dealer" TextWrapping="Wrap" Text="{Binding Dealer, UpdateSourceTrigger=PropertyChanged}" Width="auto" Foreground="#FFB0F0FF" BorderBrush="#00ABADB3" Background="Transparent" TextAlignment="Center" VerticalAlignment="Center" />
</Border>
So my class cl_Data.cs looks like:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;
using System.Windows;
namespace QuotationApp.Classes
{
internal class Cl_Data : INotifyPropertyChanged
{
#region Descriptions
private string _Dealer ;
public string Dealer
{
get { return _Dealer; }
set
{ _Dealer = value;
OnPropertyChanged("Dealer");
}
}
private string _Customer;
public string Customer
{
get { return _Customer; }
set
{
_Customer = value;
OnPropertyChanged("Customer");
}
}
private string _Machine;
public string Machine
{
get { return _Machine; }
set
{
_Machine = value;
OnPropertyChanged("Machine");
}
}
private string _PRJ;
public string PRJ
{
get { return _PRJ; }
set
{
_PRJ = value;
OnPropertyChanged(PRJ);
}
}
private string _DeliveryTime;
public string DeliveryTime
{
get { return _DeliveryTime; }
set {
_DeliveryTime = value;
OnPropertyChanged("DeliveryTime");
}
}
private string _Operation;
public string Operation
{
get { return _Operation; }
set {
_Operation = value;
OnPropertyChanged("Operation");
}
}
private string _Description;
public string Description
{
get { return _Description; }
set {
_Description = value;
OnPropertyChanged("Description");
}
}
private string _FunctionName;
public string FunctionName
{
get { return _FunctionName; }
set {
_FunctionName = value;
OnPropertyChanged("FunctionName");
}
}
private string _Requirements;
public string Requirements
{
get { return _Requirements; }
set {
_Requirements = value;
OnPropertyChanged("Requirements");
}
}
#endregion
#region Costs
private double _HardwareCost;
public double HardwareCost
{
get { return _HardwareCost; }
set {
_HardwareCost = value;
_CostTotal = CalcTotal();
OnPropertyChanged("HardwareCost");
}
}
private double _PersonalCost;
public double PersonalCost
{
get { return _PersonalCost; }
set {
_PersonalCost = value;
_CostTotal = CalcTotal();
OnPropertyChanged("PersonalCost");
}
}
private double _TravelCost;
public double TravelCost
{
get { return _TravelCost; }
set {
_TravelCost = value;
_CostTotal = CalcTotal();
OnPropertyChanged("TravelCost");
}
}
private double _CostTotal;
public double CostTotal
{
get { return _CostTotal; }
set {
_CostTotal = value;
OnPropertyChanged("CostTotal");
}
}
public double CalcTotal()
{
double total = 0;
try
{
total = TravelCost + HardwareCost + PersonalCost;
}
catch (Exception e)
{
MessageBox.Show("Error getting the total Value: " + e.Message);
}
return total;
}
#endregion
#region PropertyChangedEvents
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
}
So now i want to access these Data for example the Description (Data.Description) to process it to a word Bookmark. But how can i Access this Data on WPF level from CodeBehind?
Please be easy with me, i know this question is wierd but i googled 2 days now an i am starting to get frustrated. If this question is answered somewhere else, i would love to have the link to the answer.
Thanks in advance
I made the most simple example as far as it came to my mind.
If you don't understand, ask questions about it.
I will try to answer.
using System;
namespace Core2022.Lexxy_B
{
public class PersonDto
{
public int Id { get; }
public string Name { get; }
public int Age { get; }
public PersonDto(int id, string name, int age)
{
if (Id < 0)
throw new ArgumentOutOfRangeException(nameof(id));
Id = id;
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentNullException(nameof(name));
Name = name;
if (age < 0)
throw new ArgumentOutOfRangeException(nameof(age));
Age = age;
}
public PersonDto(string name, int age)
: this(0, name, age)
{
Id = -1;
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
namespace Core2022.Lexxy_B
{
public class PeopleModel
{
private readonly List<PersonDto> people = new List<PersonDto>()
{
new PersonDto(5, "Thomas", 25),
new PersonDto(1, "Harry", 40),
};
public IReadOnlyList<PersonDto> GetPeople() => Array.AsReadOnly(people.ToArray());
public void AddPerson(PersonDto person)
{
int id = people.LastOrDefault()?.Id ?? 0;
do
{
id++;
} while (people.Any(p => p.Id == id));
person = new PersonDto(id, person.Name, person.Age);
people.Add(person);
AddedPerson?.Invoke(this, person);
}
public event EventHandler<PersonDto>? AddedPerson;
}
}
namespace Core2022.Lexxy_B
{
public class PersonVM
{
public string? Name { get; set; }
public int Age { get; set; }
}
}
using Simplified;
using System.Collections.ObjectModel;
namespace Core2022.Lexxy_B
{
public class PeopleViewModel : ViewModelBase
{
private readonly PeopleModel model = new PeopleModel();
private string _mode = "view";
public ObservableCollection<PersonDto> People { get; } = new ObservableCollection<PersonDto>();
public string ViewMode { get => _mode; private set => Set(ref _mode, value); }
public PeopleViewModel()
{
foreach (var person in model.GetPeople())
{
People.Add(person);
}
model.AddedPerson += OnAddedPerson;
}
private void OnAddedPerson(object? sender, PersonDto newPerson)
{
People.Add(newPerson);
}
public RelayCommand AddPersonCommand => GetCommand<PersonVM>(AddPersonExecute, AddPersonCanExecute);
private void AddPersonExecute(PersonVM person)
{
model.AddPerson(new PersonDto(person.Name ?? string.Empty, person.Age));
ViewMode = "view";
}
private bool AddPersonCanExecute(PersonVM person)
{
return !string.IsNullOrWhiteSpace(person.Name) && person.Age >= 0;
}
public RelayCommand ExitAddingPersonCommand => GetCommand(() => ViewMode = "view");
public RelayCommand BeginAddingPersonCommand => GetCommand(() => ViewMode = "add");
}
}
<Window x:Class="Core2022.Lexxy_B.PeopleWindow"
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:local="clr-namespace:Core2022.Lexxy_B"
mc:Ignorable="d"
Title="PeopleWindow" Height="450" Width="800"
DataContext="{DynamicResource vm}">
<Window.Resources>
<local:PeopleViewModel x:Key="vm"/>
</Window.Resources>
<UniformGrid Columns="2">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListBox x:Name="lBox" ItemsSource="{Binding People}" DisplayMemberPath="Name"/>
<Button Grid.Row="1" Content="Go to Add Person" Padding="15 5" Margin="5"
Command="{Binding BeginAddingPersonCommand}">
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding ViewMode}" Value="add">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</Grid>
<ContentControl x:Name="cp">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Resources>
<DataTemplate x:Key="view.Template">
<local:PersonDetailsUC/>
</DataTemplate>
<DataTemplate x:Key="add.Template">
<local:AddPersonUC/>
</DataTemplate>
</Style.Resources>
<Setter Property="Content" Value="{Binding}"/>
<Setter Property="ContentTemplate" Value="{StaticResource add.Template}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ViewMode}" Value="view">
<Setter Property="Content" Value="{Binding SelectedItem, ElementName=lBox}"/>
<Setter Property="ContentTemplate" Value="{StaticResource view.Template}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</UniformGrid>
</Window>
<UserControl x:Class="Core2022.Lexxy_B.PersonDetailsUC"
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:Core2022.Lexxy_B"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance Type=local:PersonDto}">
<UniformGrid Columns="1">
<TextBlock Text="{Binding Id, StringFormat={}Id: {0}, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<TextBlock Text="{Binding Name, StringFormat={}Name: {0}}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<TextBlock Text="{Binding Age, StringFormat={}Age: {0}, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</UniformGrid>
</UserControl>
<UserControl x:Class="Core2022.Lexxy_B.AddPersonUC"
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:Core2022.Lexxy_B"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<d:UserControl.DataContext>
<local:PeopleViewModel/>
</d:UserControl.DataContext>
<UserControl.Resources>
<local:PersonVM x:Key="person"/>
</UserControl.Resources>
<UniformGrid Columns="2">
<TextBlock Text="Name" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<TextBox Text="{Binding Name, Source={StaticResource person}}" VerticalAlignment="Center" Margin="10"/>
<TextBlock Text="Age" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<TextBox Text="{Binding Age, Source={StaticResource person}}" VerticalAlignment="Center" Margin="10"/>
<Button Content="Add" Padding="15 5" VerticalAlignment="Center" HorizontalAlignment="Center"
Command="{Binding AddPersonCommand}"
CommandParameter="{Binding Mode=OneWay, Source={StaticResource person}}"/>
<Button Content="Exit" Padding="15 5" VerticalAlignment="Center" HorizontalAlignment="Center"
Command="{Binding ExitAddingPersonCommand}"/>
</UniformGrid>
</UserControl>
Addition due to the Repository.
change Line 33 of Word.cs as i was unable to assign a relative path to open to word template
An example of the implementation of obtaining the full name of files by their path given by a relatively executable assembly.
The file itself in the Project resources must have the properties "Content" - "Copy ...". I have a Russified Studio, so the screenshot is in Russian.
The "bin" folder must not be included in the Project, otherwise, all its contents will also be included in the Assembly.
And here is the code for converting a relative path to an absolute one:
public static readonly string ApplicationFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
public const string TestRelativeNameFile = #"Resources\Word\Test.docx";
public static readonly string TestFullNameFile = Path.Combine(ApplicationFolder, TestRelativeNameFile);
public static void GenerateLocalSolution()
{
try
{
WordApp = new Microsoft.Office.Interop.Word.Application();
TestApp = WordApp.Documents.Open(TestFullNameFile);
}
i bind the DataContext in MainWindow.cs (CodeBehind) to a Class and create a specific Instance of the Object which i access from every other class
This implementation doesn't play well with WPF and OOP.
For example, in Designer mode (when you edit XAML in Studio), you have an empty value in the DataContext. And because of this, you can't use the Binding Builder in development.
If you have an instance of the Cl_Data class specific to each MainWindow instance, then it should be initialized in the Window's XAML:
<Window.Resources>
<classes:Cl_Data
xmlns:classes="clr-namespace:QuotationApp.Classes"
x:Key="data"/>
</Window.Resources>
If the Cl_Data instance is the only one in the application sense and the entire session exists, then you can create it in the App resources:
<Application.Resources>
<classes:Cl_Data
xmlns:classes="clr-namespace:QuotationApp.Classes"
x:Key="data"/>
</Application.Resources>
The window gets it in the DataContext in XAML too.
If its value is needed in the Code Behind, then it must be retrieved either by key or from the DataContext.
<Window x:Class="QuoteApp_EldHasp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
------------------
Title="MainWindow" Height="900" Width="1800"
DataContext="{DynamicResource data}">
// Creating such a public property is a bad idea. But for now, I'm not getting involved.
public /* static */ Cl_Data Data { get; } //= new Cl_Data();
public MainWindow()
{
InitializeComponent();
Data = (Cl_Data)DataContext;
// Or
Data = (Cl_Data)FindResource("data");
}
Next note.
In the Word.GenerateLocalSolution() method, the line "descRange.Text = MainWindow.Data.Dealer" is a VERY STRONG OOP violation. The Word class contains logic for working with data (Domain Logic) - this is its "single-responsibility". And it should not work with UI elements. And "MainWindow.Data" is the View property!
There are several options for correct implementation.
One of them is to get the desired value in the parameter and let the one who called this method decide where to get it from.
public static void GenerateLocalSolution(string text)
{
// Some Code
descRange.Text = text; // MainWindow.Data.Dealer;
private void GenerateButton_Click(object sender, RoutedEventArgs e)
{
var data = (Cl_Data)DataContext;
Word.GenerateLocalSolution(data.Dealer);
}
You should also replace clickers with commands. This will significantly improve the architecture of the application and make the code easier.

How to reach SelectedItem in nested ListView

I'm wrestling with these last few days. I found lots of links, but none of them really helped me. I'm quite a beginner in WPF.
All I need is to reach SelectedItem property in nested ListView.
Outter ListView binding works, of course.
What I tried after some research and doesn't work, even I don't really understand why it dosnt work:
<Window x:Class="ListViewRef.View.ListViewWindow"
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:vm="clr-namespace:ListViewRef.ViewModel"
xmlns:local="clr-namespace:ListViewRef.View"
mc:Ignorable="d"
Title="Nested List Views" Height="450" Width="800">
<Window.DataContext>
<vm:MainVM/>
</Window.DataContext>
<StackPanel x:Name="Global">
<TextBlock Text="{Binding MainTitle}"/>
<ListView ItemsSource="{Binding Path=SourceCollection}"
SelectedItem="{Binding Path=OutterSelectedItem}"
>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Title}"/>
<TextBlock Text="Now second ListView:"/>
<ListView ItemsSource="{Binding Strings}"
SelectedItem="{Binding Path=NestedSelectedItem,
RelativeSource={RelativeSource AncestorType=vm:MainVM},
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"
></ListView>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</Window>
And ViewModel:
using ListViewRef.Model;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
namespace ListViewRef.ViewModel
{
public class MainVM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string mainTitle;
public string MainTitle {
get { return mainTitle; }
set { mainTitle = value; OnPropertyChanged(nameof(MainTitle)); }
}
private string nestedSelectedItem;
public string NestedSelectedItem {
get { return nestedSelectedItem; }
set
{
nestedSelectedItem = value;
MessageBox.Show("NestedSelectedItem: " + nestedSelectedItem);
OnPropertyChanged(nameof(NestedSelectedItem));
}
}
private string outterSelectedItem;
public string OutterSelectedItem {
get { return outterSelectedItem; }
set
{
outterSelectedItem = value;
MessageBox.Show("OutterSelectedItem: " + OutterSelectedItem);
OnPropertyChanged(nameof(OutterSelectedItem));
}
}
public ObservableCollection<ClassWithObsList> SourceCollection { get; set; }
public MainVM()
{
MainTitle = "Title of the Grid";
SourceCollection = new ObservableCollection<ClassWithObsList> {
new ClassWithObsList("First Title", new ObservableCollection<string> { "First", "Second"}),
new ClassWithObsList("Second Title", new ObservableCollection<string> { "Third", "Fourth"}),
new ClassWithObsList("Third Title", new ObservableCollection<string> { "Fifth", "Sixth"}),
};
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Model class:
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace ListViewRef.Model
{
public class ClassWithObsList : INotifyPropertyChanged
{
private string title;
public string Title {
get { return title; }
set {
title = value;
OnPropertyChanged(nameof(Title));
}
}
public ObservableCollection<string> Strings { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public ClassWithObsList(string title, ObservableCollection<string> strings)
{
Title = title ?? throw new ArgumentNullException(nameof(title));
Strings = strings ?? throw new ArgumentNullException(nameof(strings));
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
In
SelectedItem="{Binding Path=NestedSelectedItem,
RelativeSource={RelativeSource AncestorType=vm:MainVM}, ...}
the type vm:MainVM is not an ancestor type of the inner ListView, because it not part of a visual or logical tree.
The AncestorType must be a UI element, e.g. the outer ListView. You would access the property by a nested property Path via its DataContext:
SelectedItem="{Binding Path=DataContext.NestedSelectedItem,
RelativeSource={RelativeSource AncestorType=ListView}, ...}
As a note, since you are not setting the View property of the ListViews, you could as well use the simpler base class ListBox instead of ListView:
<ListBox ItemsSource="{Binding Path=SourceCollection}"
SelectedItem="{Binding Path=OutterSelectedItem}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Title}"/>
<TextBlock Text="Now second ListView:"/>
<ListBox ItemsSource="{Binding Strings}"
SelectedItem="{Binding Path=DataContext.NestedSelectedItem,
RelativeSource={RelativeSource AncestorType=ListBox},
Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

How to sync two lists oneway?

I have a list of Label objects presented in a ListBox. The Labels can be assigned Fonts which may be selected from a ComboBox. The Label object references the Font object through the FontId.
When selecting a Label from the ListBox, the according Font object shall be selected in the ComboBox. However, selecting a Font from the ComboBox shall 'assign' the font to the selected Label without selecting a matching item in the ListBox. That's why I called that 'oneway' sync.
My current code syncs the two lists in both directions, i.e. selecting a Font object from the ComboBox results in selecting the Label with the corresponding font id in the ListBox.
Below you'll find the ViewModel with the Label and Font models as well as the XAML.
using System.Collections.Generic;
using System.ComponentModel;
namespace WpfApplication1
{
public class NotifyPropertyChanged : System.ComponentModel.INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string prop)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
}
public class ViewModel : NotifyPropertyChanged
{
public class Label : NotifyPropertyChanged
{
public Label(string name, int id)
{
this.name = name;
this.fontId = id;
}
string name;
public string Name
{
get { return name; }
set
{
if (name == value) return;
name = value;
RaisePropertyChanged("Name");
}
}
int fontId;
public int FontId
{
get { return fontId; }
set
{
if (fontId == value) return;
fontId = value;
RaisePropertyChanged("FontId");
}
}
}
public class Font : NotifyPropertyChanged
{
public Font(string face, int id)
{
this.face = face;
this.id = id;
}
int id;
public int Id
{
get { return id; }
set
{
if (id == value) return;
id = value;
RaisePropertyChanged("Id");
}
}
string face;
public string Face
{
get { return face; }
set
{
if (face == value) return;
face = value;
RaisePropertyChanged("Face");
}
}
}
List<Label> labels = new List<Label>
{
new Label("City", 1),
new Label("Road", 13),
new Label("POI", 17),
new Label("Favorite", 42)
};
public IEnumerable<Label> Labels
{
get { return labels; }
}
List<Font> fonts = new List<Font>
{
new Font("Arial 20", 1),
new Font("Arial 10", 13),
new Font("Arial 8", 17),
new Font("Arial 12", 42),
new Font("Times 12", 47),
new Font("Times 18", 11)
};
public IEnumerable<Font> Fonts
{
get { return fonts; }
}
Label curLabel;
public Label CurrentLabel
{
get { return curLabel; }
set
{
if (curLabel == value) return;
curLabel = value;
RaisePropertyChanged("CurrentLabel");
}
}
Font curFont;
public Font CurrentFont
{
get { return curFont; }
set
{
if (curFont == value) return;
curFont = value;
RaisePropertyChanged("CurrentFont");
}
}
}
}
And here the XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="216" ResizeMode="NoResize" SizeToContent="Width">
<WrapPanel Margin="10">
<ListBox Name="labelListBox" Width="160" Height="130" Margin="10"
ItemsSource="{Binding Labels}"
DisplayMemberPath="Name"
SelectedItem="{Binding CurrentLabel}"
SelectedValuePath="FontId"/>
<ComboBox Name="fontComboBox" Width="160" Height="30" Margin="10" VerticalAlignment="Top"
ItemsSource="{Binding Fonts}"
DisplayMemberPath="Face"
SelectedValuePath="Id"
SelectedValue="{Binding ElementName=labelListBox, Path=SelectedValue}"/>
</WrapPanel>
</Window>
And the code-behind:
using System.Windows;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
}
}
Thanks for hints!
The simple solution is setting Mode of the Binding (in ComboBox) to OneWay:
<ComboBox Name="fontComboBox" Width="160" Height="30" Margin="10"
VerticalAlignment="Top"
ItemsSource="{Binding Fonts}"
DisplayMemberPath="Face"
SelectedValuePath="Id"
SelectedValue="{Binding ElementName=labelListBox, Path=SelectedValue,
Mode=OneWay}"/>
Now when selecting in ListBox, its SelectedValue will change, making the SelectedValue of ComboBox change. The SelectedValuePath resolves the actual SelectedValue to the Id member and will select the matched item. Because we set the Binding's Mode to OneWay, selecting item from ComboBox as well as changing the SelectedValue of the ComboBox won't reflect to the ListBox's SelectedValue.
Minor correction in your xaml as below. Hope this is what you want and it helps.
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication2"
Title="MainWindow" Height="216" ResizeMode="NoResize" SizeToContent="Width">
<WrapPanel Margin="10">
<ListBox Name="labelListBox" Width="160" Height="130" Margin="10"
ItemsSource="{Binding Labels}"
DisplayMemberPath="Name"
SelectedItem="{Binding CurrentLabel}"
FontSize="{Binding ElementName=fontComboBox , Path=SelectedValue.Id}"
FontFamily="{Binding ElementName=fontComboBox, Path=SelectedValue.Face}"/>
<ComboBox Name="fontComboBox" Width="160" Height="30" Margin="10" VerticalAlignment="Top"
ItemsSource="{Binding Fonts}"
DisplayMemberPath="Face"
SelectedValue="{Binding CurrentFont}"/>
</WrapPanel>

ListView's item insert animations in WinRT

Recently I started building my own big Windows 8 Store App.
Working on UI I started replicating some good UIs.
One I met very interesting animation of inserting new elements in list view in standard Mail app. When you click on chain it expands and shows all messages in chain.
Here is captured video.
I have no idea what technique did they use to achieve this animation and behavior.
Can anyone help me, explain or give example how can I achieve such behavior? Thanks.
The mail app is written in JavaScript, so it won't help you much to know how it was done since this UI stack is quite different than the XAML one. The thing though is that the list controls are likely animated the same way, so you only need to add/remove some items in the list to get the expansion/collapse effect.
I played with it for a bit and this is what I came up with that uses ListView's ItemTemplateSelector property to define a few different item templates.
<Page
x:Class="App82.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App82"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Page.Resources>
<local:CollapsibleListItemTemplateSelector
x:Key="collapsibleListItemTemplateSelector">
<local:CollapsibleListItemTemplateSelector.BasicItemTemplate>
<DataTemplate>
<Border
Margin="5"
Height="50"
VerticalAlignment="Stretch"
BorderBrush="ForestGreen"
BorderThickness="2,0,0,0">
<StackPanel
Margin="10,0,0,0">
<TextBlock
FontWeight="Bold"
Text="{Binding Title}" />
<TextBlock
Text="{Binding Gist}" />
</StackPanel>
</Border>
</DataTemplate>
</local:CollapsibleListItemTemplateSelector.BasicItemTemplate>
<local:CollapsibleListItemTemplateSelector.ExpandedItemTemplate>
<DataTemplate>
<Border
Margin="15,5,5,5"
Height="50"
VerticalAlignment="Stretch"
BorderBrush="Yellow"
BorderThickness="2,0,0,0">
<StackPanel
Margin="10,0,0,0">
<TextBlock
FontWeight="Bold"
Text="{Binding Title}" />
<TextBlock
Text="{Binding Gist}" />
</StackPanel>
</Border>
</DataTemplate>
</local:CollapsibleListItemTemplateSelector.ExpandedItemTemplate>
<local:CollapsibleListItemTemplateSelector.CollapsibleItemTemplate>
<DataTemplate>
<Border
Margin="5"
Height="50"
VerticalAlignment="Stretch"
BorderBrush="DodgerBlue"
BorderThickness="2,0,0,0">
<StackPanel
Margin="10,0,0,0"
Orientation="Horizontal">
<TextBlock
FontWeight="Bold"
Text="{Binding ChildItems.Count}" />
<TextBlock
FontWeight="Bold"
Text=" Items" />
</StackPanel>
</Border>
</DataTemplate>
</local:CollapsibleListItemTemplateSelector.CollapsibleItemTemplate>
</local:CollapsibleListItemTemplateSelector>
</Page.Resources>
<Grid
Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<ListView
x:Name="ListView"
ItemTemplateSelector="{StaticResource collapsibleListItemTemplateSelector}"
ItemClick="OnItemClick"
IsItemClickEnabled="True" />
</Grid>
</Page>
Code behind:
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using App82.Common;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace App82
{
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
var items = new ObservableCollection<BindableBase>();
var item1 = new BasicItem { Title = "Item 1", Gist = "This item has some content that is not fully shown..." };
var item2 = new ExpandedItem { Title = "Item 2", Gist = "This item has some content that is not fully shown..." };
var item3 = new ExpandedItem { Title = "Item 3", Gist = "This item has some content that is not fully shown..." };
var item4 = new ExpandedItem { Title = "Item 4", Gist = "This item has some content that is not fully shown..." };
var item5 = new BasicItem { Title = "Item 5", Gist = "This item has some content that is not fully shown..." };
var itemGroup1 = new CollapsibleItem(items, new[] { item2, item3, item4 });
items.Add(item1);
items.Add(itemGroup1);
items.Add(item5);
this.ListView.ItemsSource = items;
}
private void OnItemClick(object sender, ItemClickEventArgs e)
{
var collapsibleItem = e.ClickedItem as CollapsibleItem;
if (collapsibleItem != null)
collapsibleItem.ToggleCollapse();
}
}
public class CollapsibleListItemTemplateSelector : DataTemplateSelector
{
public DataTemplate BasicItemTemplate { get; set; }
public DataTemplate CollapsibleItemTemplate { get; set; }
public DataTemplate ExpandedItemTemplate { get; set; }
protected override Windows.UI.Xaml.DataTemplate SelectTemplateCore(object item, Windows.UI.Xaml.DependencyObject container)
{
if (item is ExpandedItem)
return ExpandedItemTemplate;
if (item is BasicItem)
return BasicItemTemplate;
//if (item is CollapsibleItem)
return CollapsibleItemTemplate;
}
}
public class BasicItem : BindableBase
{
#region Title
private string _title;
public string Title
{
get { return _title; }
set { this.SetProperty(ref _title, value); }
}
#endregion
#region Gist
private string _gist;
public string Gist
{
get { return _gist; }
set { this.SetProperty(ref _gist, value); }
}
#endregion
}
public class ExpandedItem : BasicItem
{
}
public class CollapsibleItem : BindableBase
{
private readonly IList _hostCollection;
#region IsExpanded
private bool _isExpanded;
public bool IsExpanded
{
get { return _isExpanded; }
set
{
if (this.SetProperty(ref _isExpanded, value))
{
if (_isExpanded)
Expand();
else
Collapse();
}
}
}
#endregion
#region ChildItems
private ObservableCollection<BasicItem> _childItems;
public ObservableCollection<BasicItem> ChildItems
{
get { return _childItems; }
set { this.SetProperty(ref _childItems, value); }
}
#endregion
public CollapsibleItem(
IList hostCollection,
IEnumerable<BasicItem> childItems)
{
_hostCollection = hostCollection;
_childItems = new ObservableCollection<BasicItem>(childItems);
}
public void ToggleCollapse()
{
IsExpanded = !IsExpanded;
}
private void Expand()
{
int i = _hostCollection.IndexOf(this) + 1;
foreach (var childItem in ChildItems)
{
_hostCollection.Insert(i++, childItem);
}
}
private void Collapse()
{
int i = _hostCollection.IndexOf(this) + 1;
for (int index = 0; index < ChildItems.Count; index++)
{
_hostCollection.RemoveAt(i);
}
}
}
}

Can I update the value of a WPF binding from the C# code behind?

I’m learning C# and building a UI that reads and writes integers to an XML config file. The UI uses a variety of custom user controls. I have a 3 radiobutton user control that binds to a single int variable (control returns 0,1,2). The control uses an event to trigger the update. It looks at the 3 isChecked properties to determine the new int value. But I don’t know how to update the original binding value from the code behind. It's once removed so to speak because there are two binds..one in the main window and one in the user control. As a beginner am lost at this point. BTW reading the int value into the 3 radiobuttons is working using a converter.
here is the user control xaml.cs...
namespace btsRV7config.controls
{
public partial class ui3X : UserControl
{
public ui3X()
{
InitializeComponent();
}
void _event(object sender, RoutedEventArgs e)
{
int newValue = 0;
if (rdbttn1.IsChecked == true) { newValue = 0; }
else if (rdbttn2.IsChecked == true) { newValue = 1; }
else if (rdbttn3.IsChecked == true) { newValue = 2; }
txtb.Text = newValue.ToString(); //tempRemove
// !!! assign newValue to Binding Source !!!
//---------------------------------------------
uiBinding1 = newValue;
BindingOperations.GetBindingExpression(rdbttn1, RadioButton.IsCheckedProperty).UpdateSource();
//---------------------------------------------
}
public static readonly DependencyProperty uiBinding1Property = DependencyProperty.Register("uiBinding1", typeof(int), typeof(ui3X));
public int uiBinding1
{
get { return (int)GetValue(uiBinding1Property); }
set { SetValue(uiBinding1Property, value); }
}
public static readonly DependencyProperty uiBinding2Property = DependencyProperty.Register("uiBinding2", typeof(int), typeof(ui3X));
public int uiBinding2
{
get { return (int)GetValue(uiBinding2Property); }
set { SetValue(uiBinding2Property, value); }
}
public static readonly DependencyProperty uiBinding3Property = DependencyProperty.Register("uiBinding3", typeof(int), typeof(ui3X));
public int uiBinding3
{
get { return (int)GetValue(uiBinding3Property); }
set { SetValue(uiBinding3Property, value); }
}
}
}
here is user control xaml
<UserControl x:Class="btsRV7config.controls.ui3X"
x:Name="root"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel Orientation="Horizontal" Height="22">
<RadioButton Name="rdbttn1" VerticalAlignment="Center" Margin="0 0 10 0"
IsChecked="{Binding ElementName=root, Path=uiBinding1}"
Click="_event" />
<RadioButton Name="rdbttn2" VerticalAlignment="Center" Margin="0 0 10 0"
IsChecked="{Binding ElementName=root, Path=uiBinding2}"
Click="_event" />
<RadioButton Name="rdbttn3" VerticalAlignment="Center"
IsChecked="{Binding ElementName=root, Path=uiBinding3}"
Click="_event" />
<TextBox Name="txtb" Margin="5 0 0 0" Width="20" Height="17" /> <!-- tempRemove -->
</StackPanel>
</UserControl>
here is an example of the user control used in MainWindow.xaml
<Window x:Class="btsRV7config.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:btsRV7config.controls"
xmlns:converter="clr-namespace:btsRV7config.converters"
Title="Vans RV7 Configuration" Height="350" Width="525" >
<Window.Resources>
<converter:Int_Int_Bool_Converter x:Key="Int_Int_Bool" />
</Window.Resources>
<Grid>
<controls:ui3X uiName="Font Color" ui1="Black" ui2="Green" ui3="Cyan"
uiBinding1="{Binding RV7sld_DFfontColor, Converter={StaticResource Int_Int_Bool}, ConverterParameter=0}"
uiBinding2="{Binding RV7sld_DFfontColor, Converter={StaticResource Int_Int_Bool}, ConverterParameter=1}"
uiBinding3="{Binding RV7sld_DFfontColor, Converter={StaticResource Int_Int_Bool}, ConverterParameter=2}" />
</Grid>
</Window>
here is MainWindow.xaml.cs
namespace btsRV7config
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
record data = new record();
DataContext = data;
}
}
public class record : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int _RV7sld_DFfontColor = RV7sld_dict["DFfontColor"];
public int RV7sld_DFfontColor
{
get
{ return _RV7sld_DFfontColor; }
set
{
_RV7sld_DFfontColor = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("RV7sld_DFfontColor"));
}
}
}
}
}
Sorry for posting so much code - I think the important is the user controls xaml.cs at top.
here is a link to a picture of the UI.
I've simplified the code I've posted to fit.
http://www.baytower.ca/photo/uiSample.jpg
So - 'Font Color'(RV7sld_DFfontColor) can be black(0) green(1) cyan(2)
Danny
The BindingOperations class enables you to "force" the binding updates in either direction.
Let's say there is a string property X in the code behind and there is a TextBox in XAML, bound to that property:
// C#:
public string X { get; set; }
// XAML:
<TextBox Name="textBox1" Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=my:MainWindow, AncestorLevel=1}, Path=X}" />
To copy from textBox1.Text to X do the following:
BindingOperations.GetBindingExpression(textBox1, TextBox.TextProperty).UpdateSource();
To copy from X to textBox1.Text do the following:
BindingOperations.GetBindingExpression(textBox1, TextBox.TextProperty).UpdateTarget();

Categories