Change style based on data - c#

Consider below XAML =>
<fluent:Ribbon x:Name="MenuRibbon"
Title="title"
SnapsToDevicePixels="True">
<fluent:RibbonTabItem x:Name="Home"
Header="Home">
<fluent:RibbonGroupBox Header="Project">
<fluent:InRibbonGallery MinItemsInRow="3"
MaxItemsInRow="6"
Width="300"
ItemWidth="64"
ItemHeight="56"
ItemsSource="{Binding Projects}">
<fluent:InRibbonGallery.ItemTemplate>
<DataTemplate>
<DockPanel>
<Border BorderBrush="{x:Null}" Height="56">
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Center" DockPanel.Dock="Bottom"
Text="{Binding Name}">
</TextBlock>
</Border>
</DockPanel>
</DataTemplate>
</fluent:InRibbonGallery.ItemTemplate>
</fluent:InRibbonGallery>
</fluent:RibbonGroupBox>
</fluent:RibbonTabItem>
</fluent:Ribbon>
I've bind ObservableColleciton of Projects to InRibbonGallery , And there is an instance of project (ActiveProject) exist in ViewModel.
a TextBlock defined in DataTemplate to display Name of Project object.
How can I change color of TextBlock that contains Active Project ?
ViewModel :
public class ViewModel : ViewModelBase
{
private ObservableCollection<Project> _projects;
public ViewModel()
{
Projects = new ObservableCollection<Project>(new List<Project>
{
new Project {Id = "0", Name = "Project1"},
new Project {Id = "1", Name = "Project2"},
new Project {Id = "2", Name = "Project3"}
});
Project = Projects[0];
}
public ObservableCollection<Project> Projects
{
get { return _projects; }
set
{
_projects = value;
RaisePropertyChanged(() => Projects);
}
}
public Project Project { get; set; }
}
public class Project : ObservableObject
{
private string _id;
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
RaisePropertyChanged(() => Name);
}
}
public string Id
{
get { return _id; }
set
{
_id = value;
RaisePropertyChanged(() => Id);
}
}
}
Project files located here .

You can do this by using a MultiBinding and a DataTrigger in combination for a better result.
so your xaml would look like:
<Window.Resources>
<vm:ViewModel x:Key="ViewModel" />
<vm:ActiveProjectCheckConverter x:Key="ActiveProjectCheckConverter" />
</Window.Resources>
...
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
DockPanel.Dock="Bottom"
Text="{Binding Name}">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Background"
Value="Transparent" />
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource ActiveProjectCheckConverter}">
<Binding Path="Name" />
<Binding Path="DataContext.ActiveProject.Name"
RelativeSource="{RelativeSource FindAncestor,
AncestorType={x:Type fluent:InRibbonGallery}}" />
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Background">
<Setter.Value>
<LinearGradientBrush StartPoint="1,0">
<GradientStop Offset="0.0"
Color="#00FFFFFF" />
<GradientStop Offset="1.1"
Color="#FFFFFFFF" />
</LinearGradientBrush>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
and your converter:
public class ActiveProjectCheckConverter : IMultiValueConverter {
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {
string first = values[0] as string;
string second = values[1] as string;
return !string.IsNullOrEmpty(first) && !string.IsNullOrEmpty(second) && first == second;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) {
throw new NotImplementedException();
}
}
Now I did make one more change, your Project object in your ViewModel, if you want changes to that to reflect in the View you need to make that implement INPC itself. So I did update that and also renamed it to ActiveProject
private Project _activeProject;
public Project ActiveProject {
get {
return _activeProject;
}
set {
if (value == _activeProject)
return;
_activeProject = value;
RaisePropertyChanged(() => ActiveProject);
}
}
Update
You can find the above updates at: Dropbox-Link

Related

Cannot find governing FrameworkElement

I have read lot of answer about this type of error but nothings to fix my problem.
my program runs with this error, but i would know why and how i could fix? or its known bug? Thnaks for your help
error for Appearance.Offset.X:
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element.
BindingExpression:Path=Appearance.Offset.X; DataItem=null; target element is 'TranslateTransform' (HashCode=62775401); target property is 'X' (type 'Double')
Same error for Appearance.Offset.Y
my xaml file
<UserControl
:
:
mc:Ignorable="d" d:DesignHeight="150" d:DesignWidth="70"
x:Name="usercontrol" ToolTip="{Binding ToolTip}"
VerticalAlignment = "Top" HorizontalAlignment = "Left" ClipToBounds="True"
cal:Message.Attach="[Event MouseEnter] = [Action MouseEnterInUC($eventArgs)];
[Event MouseLeftButtonDown] = [Action MouseLeftButtonDownOnUC($source, $mousepoint, $eventArgs)];
[Event MouseLeftButtonUp] = [Action MouseLeftButtonUp()]">
<Grid x:Name="Switch" Width="{Binding Path=Layout.Width}" Height="{Binding Path=Layout.Height}" >
<Image x:Name="Pushed" Source="{Binding Appearance.PushedImage, Mode=TwoWay}"
HorizontalAlignment="Center" VerticalAlignment="Center" >
:
:
</Image>
<Image x:Name="Normal" Source="{Binding Appearance.Image}"
HorizontalAlignment="Center" VerticalAlignment="Center" >
:
:
</Image>
<TextBlock Text="{Binding Appearance.GlyphText}" Foreground="{Binding Appearance.TextColor, Converter={StaticResource MyconverterColorToSolidColorBrush}}"
HorizontalAlignment="{Binding Appearance.SelectedHAlignType}" VerticalAlignment="{Binding Appearance.SelectedVAlignType}"
>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding Appearance.IndexImage}" Value="1">
<Setter Property="RenderTransform" >
<Setter.Value>
Error here-----> <TranslateTransform X="{Binding Appearance.Offset.X}" Y="{Binding Appearance.Offset.Y}" />
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<Path StrokeThickness="{Binding Appearance.GlyphThickness}" Stroke="{Binding Appearance.GlyphColor, Converter={StaticResource MyconverterColorToSolidColorBrush}}" >
:
:
</Path>
</Grid>
</UserControl>
ViewModel file:
namespace Cockpit.Core.Plugins.Plugins
{
[Identity(GroupName = "PushButton", Name ="", Type = typeof(PushButton_ViewModel))]
[DataContract(Name = "Cockpit.Core.Plugins.Plugins.PushButton_ViewModel")]
public class PushButton_ViewModel : PropertyChangedBase, IPluginModel
{
private readonly IEventAggregator eventAggregator;
[DataMember] public PushButtonAppearanceViewModel Appearance { get; private set; }
public PushButton_ViewModel(IEventAggregator eventAggregator, params object[] settings)
{
Appearance = new PushButtonAppearanceViewModel(settings);
NameUC = (string)settings[2];
this.eventAggregator = eventAggregator;
System.Diagnostics.Debug.WriteLine($"entree push {NameUC} {this}");
}
}
}
Appearance ViewModel:
namespace Cockpit.Core.Plugins.Plugins.Properties
{
[DataContract]
public class PushButtonAppearanceViewModel : PropertyChangedBase, IPluginProperty
{
public string NameUC { get; set; }
public PushButtonAppearanceViewModel(params object[] settings)
{
:
:
Name = "Appearance";
}
public string Name { get; set; }
private TextFormat textformat;
public TextFormat TextFormat
{
get => textformat;
set
{
textformat = value;
NotifyOfPropertyChange(() => TextFormat);
}
}
private string textPushOffset;
public string TextPushOffset
{
get => textPushOffset;
set
{
textPushOffset = value;
var a = value.Split(',').Select(i => Convert.ToInt32(i)).ToArray();
Offset = new Point(a[0], a[1]);
NotifyOfPropertyChange(() => TextPushOffset);
}
}
private Point offset;
public Point Offset
{
get => offset;
set
{
offset = value;
NotifyOfPropertyChange(() => Offset);
}
}
}
}
This might help you out.
Datatemplate binding spam Output window with error: Cannot find governing FrameworkElemen
The accepted answer says that a transform doesn't live in the visual or logical tree, so it can't inherit the data context needed to complete the binding.
The recommended solution is to define the transform as a resource of your TextBlock:
<TextBlock ...>
<TextBlock.Resources>
<TranslateTransform x:Key="MyTransform" X="{Binding Appearance.Offset.X}" Y="{Binding Appearance.Offset.Y}" />
</TextBlock.Resources>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding Appearance.IndexImage}" Value="1">
<Setter Property="RenderTransform" >
<Setter.Value>
<StaticResource ResourceKey="MyTransform" />
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
I hope this is helpful.

WPF - Changes in treeviewitems' properties not reflected in UI problem

I have a tree view built with HierarchicalDataTemplates, I want to be able to add JSON files to SegmentInfo nodes - if I do so, the data is added but the change is not reflected in UI (still the comment says "no data" in red).
I've made the list the tree view items as ObservableCollection, moved it to a "ViewModel" class that inherits INotifyPropertyChanged, I seem to set it up properly, I've set DataContext to the ViewModel object in my Window.
In xaml I've set the bindings and mode as TwoWay. Still nothing helped
XAML:
<Window.Resources>
<local:BoolToStringConverter x:Key="BoolToStringConverter" FalseValue="no data" TrueValue="has data" />
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" MinHeight="384.8"/>
<RowDefinition Height="35.2"/>
</Grid.RowDefinitions>
<TreeView Name="trvTypeInfos" Margin="5" Grid.Row="0" ItemsSource="{Binding Path=TypeInfoList, Mode=TwoWay}">
<TreeView.Resources>
<Style TargetType="{x:Type TreeViewItem}">
<EventSetter Event="ListBoxItem.PreviewMouseUp"
Handler="ListBoxItem_PreviewMouseUp"/>
<Setter Property="IsExpanded" Value="True"/>
</Style>
<HierarchicalDataTemplate DataType="{x:Type data:TypeInfo}" ItemsSource="{Binding SegmentInfos, Mode=TwoWay}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<TextBlock Text=" [" Foreground="Blue" />
<TextBlock Text="{Binding SegmentInfos.Count}" Foreground="Blue"/>
<TextBlock Text="]" Foreground="Blue" />
</StackPanel>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type data:SegmentInfo}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<TextBlock Text=" ("/>
<TextBlock Text="{Binding Path=HasData, Mode=TwoWay, Converter={StaticResource BoolToStringConverter}}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<Trigger Property="Text" Value="no data">
<Setter Property="Foreground" Value="Red"/>
</Trigger>
<Trigger Property="Text" Value="has data">
<Setter Property="Foreground" Value="Green"/>
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock Text=")"/>
</StackPanel>
</DataTemplate>
</TreeView.Resources>
</TreeView>
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Width="80" Height="20" Content="OK" Margin="5,0, 5, 5" IsDefault="True" Click="OK_Click"/>
<Button Width="80" Height="20" Content="Cancel" Margin="5,0, 5, 5" Click="Cancel_Click" />
</StackPanel>
</Grid>
Window class:
public SegmentDataUpdaterDialog(SegmentDataUpdater segmentDataUpdater, List<TypeInfo> typeInfoList)
{
ViewModel = new ViewModel(typeInfoList);
DataContext = ViewModel;
SegmentDataUpdater = segmentDataUpdater;
InitializeComponent();
}
private void ListBoxItem_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
TreeViewItem item = sender as TreeViewItem;
SegmentInfo segInfo = item.Header as SegmentInfo;
if (segInfo != null)
{
MessageBox.Show(segInfo.JsonContents);
var filePath = AskForFile();
bool success = SegmentDataUpdater.TryStoreJson(segInfo, filePath, out string json);
if (success)
{
segInfo.JsonContents = json;
segInfo.HasData = true;
}
}
}
ViewModel class:
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<TypeInfo> _typeInfoList;
public ObservableCollection<TypeInfo> TypeInfoList
{
get { return _typeInfoList; }
set
{
if (_typeInfoList==null || !value.All(_typeInfoList.Contains))
{
_typeInfoList = value;
OnPropertyChanged(nameof(TypeInfoList));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public ViewModel(List<TypeInfo> typeInfos)
{
TypeInfoList = new ObservableCollection<TypeInfo>(typeInfos);
}
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
TypeInfo class:
public class TypeInfo
{
public string Name { get; set; }
public ObservableCollection<SegmentInfo> SegmentInfos { get; set; }
public int ElementId { get; set; }
public TypeInfo()
{
SegmentInfos = new ObservableCollection<SegmentInfo>();
}
}
SegmentInfo class:
public class SegmentInfo
{
public string Name { get; set; }
public bool HasData { get; set; }
public string JsonContents { get; set; }
public int ElementId { get; set; }
}
Converter classes:
public class BoolToValueConverter<T> : IValueConverter
{
public T FalseValue { get; set; }
public T TrueValue { get; set; }
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null)
return FalseValue;
else
return (bool)value ? TrueValue : FalseValue;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value != null ? value.Equals(TrueValue) : false;
}
}
public class BoolToStringConverter : BoolToValueConverter<String> { }
I expect that after successful adding json file to the SegmentInfo the UI will update the node with "has data" comment.
Now I can check that the data is really added to the SegmentInfo but UI doesn't reflect that.
Your HasData property does not update the UI, as you have no mechanism to update it (INotifyPropertyChanged). SegmentInfo needs to implement INotifyPropertyChanged.
If you plan to have a property Bind to the UI, it needs to have an PropertyChanged Notification go out for it individually. So on your SegmentInfo class; Name, HasData, and JsonContent should each raise an OnPropertyChanged event in their setter.
A good way to think of it; anything that is directly bound in XAML (Text="{Binding Name}") should raise an event when changed. If you bind any properties like: (Text="{Binding MyThing.Name}") you will not get an update when MyThing.Name changes. You need to break out the property and Notify on it directly.

How to manipulate specific ListViewItem from ListView

I want to mark a ListViewItem from a ListView by changing the foreground color of the textblock which is in the listviewitem. I want to do that dynamically. Whenever a user selects the item, the TextBlock's foreground color should be changed. Here is the Templete
<ListView Name="SongsListView"
IsItemClickEnabled="True"
ItemClick="SongsListView_ItemClick"
ItemsSource="{x:Bind rootpage.Songs}"
VerticalAlignment="Top"
HorizontalAlignment="Stretch">
<ListView.ItemTemplate>
<DataTemplate x:DataType="model:Song">
<controls:DropShadowPanel ShadowOpacity="0.20"
Color="Black"
HorizontalContentAlignment="Stretch"
BlurRadius="10"
OffsetX="0"
OffsetY="7.0">
<Grid HorizontalAlignment="Stretch" CornerRadius="5" Background="{ThemeResource SystemControlAltHighAcrylicElementBrush}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{x:Bind Name}" Name="songNameTextBlock" TextWrapping="Wrap"/>
<TextBlock Text="{x:Bind Artist}" Name="ArtistNameTextBlock" TextWrapping="Wrap"/>
</StackPanel>
</Grid>
</controls:DropShadowPanel>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="Margin" Value="4"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
private void SongsListView_ItemClick(object sender, ItemClickEventArgs e)
{
//Please write the code for me !
}
I would suggest you to define a foreground relevant property in your Song class and bind it to the TextBlock's Foreground property. Then you could use Binding/{x:Bind} to change its foreground color when it's selected, instead of using the ItemClick event handler method to find the TextBlock controls in it.
Please refer to the following code sample for details:
<ListView Name="SongsListView"
IsItemClickEnabled="True"
ItemsSource="{x:Bind Songs}"
VerticalAlignment="Top"
HorizontalAlignment="Stretch" SelectedItem="{x:Bind currentSelectedItem,Mode=TwoWay,Converter={StaticResource myconverter}}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="model:Song">
<controls:DropShadowPanel ShadowOpacity="0.20"
Color="Black"
HorizontalContentAlignment="Stretch"
BlurRadius="10"
OffsetX="0"
OffsetY="7.0">
<Grid HorizontalAlignment="Stretch" CornerRadius="5" Background="{ThemeResource SystemControlAltHighAcrylicElementBrush}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{x:Bind Name}" Name="songNameTextBlock" TextWrapping="Wrap" Foreground="{x:Bind customcolor,Mode=OneWay}"/>
<TextBlock Text="{x:Bind Artist}" Name="ArtistNameTextBlock" TextWrapping="Wrap" Foreground="{x:Bind customcolor,Mode=OneWay}"/>
</StackPanel>
</Grid>
</controls:DropShadowPanel>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="Margin" Value="4"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
<Page.Resources>
<local:MyConverter x:Key="myconverter"></local:MyConverter>
</Page.Resources>
public sealed partial class MainPage : Page
{
public ObservableCollection<Song> Songs { get; set; }
private Song _currentSelectedItem;
public Song currentSelectedItem
{
get { return _currentSelectedItem; }
set
{
if (_currentSelectedItem != null)
{
_currentSelectedItem.customcolor = new SolidColorBrush(Colors.Black);
}
_currentSelectedItem = value;
_currentSelectedItem.customcolor = new SolidColorBrush(Colors.Red);
}
}
public MainPage()
{
this.InitializeComponent();
Songs = new ObservableCollection<Song>();
Songs.Add(new Song() {Name="abc",Artist="Singer1" });
Songs.Add(new Song {Name="def",Artist="Singer2" });
}
}
public class Song : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string PropertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
private string _Name;
public string Name
{
get { return _Name; }
set
{
_Name = value;
RaisePropertyChanged("Name");
}
}
private string _Artist;
public string Artist
{
get { return _Artist; }
set
{
_Artist = value;
RaisePropertyChanged("Artist");
}
}
private SolidColorBrush _customcolor = new SolidColorBrush(Colors.Black);
public SolidColorBrush customcolor
{
get { return _customcolor; }
set
{
_customcolor = value;
RaisePropertyChanged("customcolor");
}
}
}
public class MyConverter:IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
return value as Song;
}
}
Please note that I make a converter class and use it when I bind currentSelectedItem to ListView's SelectedItem. Because I used x:Bind, it's compile time. If you do not add a converter, you will get error Invalid binding path 'currentSelectedItem' : Cannot bind type 'AppListView.model.Song' to 'System.Object' without a converter.
Besides, you could see I set Mode=OneWay when I use {x:Bind} to bind customcolor to TextBlock's foreground. It's due to the default value of {x:Bind}'s mode is OneTime. If you do not change OneTime to OneWay, you will not see the foreground color changed.

Listbox groupstyle bind expander IsExpanded property

I have a list with users that are ordered in some groups.
This is my ListBox xaml :
<ListBox x:Name="UserContainer"
ItemsSource="{Binding allUserViewModel.UserView}"
Background="Transparent"
HorizontalContentAlignment="Stretch"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Auto"
BorderThickness="0" Margin="0,0,0,7" Padding="0"
ItemContainerStyle="{DynamicResource ListBoxItemStyle}"
Visibility="{Binding allUserViewModel.UserView.Count, Converter={StaticResource ShowIfHasUsersConverter}}"
MouseEnter="UserContainer_OnMouseEnter" MouseLeave="UserContainer_OnMouseLeave">
<ListBox.Style>
<Style>
<Style.Triggers>
<Trigger Property="ListBox.IsGrouping" Value="true">
<Setter Property="ScrollViewer.CanContentScroll" Value="false" />
</Trigger>
</Style.Triggers>
</Style>
</ListBox.Style>
<ListBox.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="{Binding Path=Name.IsExpanded}" Style="{StaticResource EditedMetroExpander}" Padding="0,3,0,3">
<Expander.Header>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
<TextBlock Text="{Binding Name}" Foreground="Silver"
FontSize="18" FontFamily="Segoe UI Light" VerticalAlignment="Center" />
</StackPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListBox.GroupStyle>
<ListBox.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="Transparent" />
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightTextBrushKey}" Color="Black" />
<SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}" Color="Transparent" />
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<view:UserControlButton x:Name="UserControlButton" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
This is a list of users. Each user has a userviewmodel with a certain groupId to see in which group he should be.
So in my mainviewmodel which contains a list of those userviewmodels I set my groupdescriptions
public AllUserViewModel()
{
UserList = new ObservableCollection<UserViewModel>();
OnlineContacts = new List<UserViewModel>();
LocalContacts = new List<UserViewModel>();
UserView = (CollectionView)CollectionViewSource.GetDefaultView(UserList);
// Filter
if (UserView.CanFilter)
UserView.Filter = OnFilterUsers;
// Group
if (UserView.CanGroup && UserView.GroupDescriptions != null)
UserView.GroupDescriptions.Add(new PropertyGroupDescription(UserViewModel.GroupIdPropertyKey, new GroupDescriptionConverter()));
// Sorting
if (UserView.CanSort)
{
// Custom sort only available with ListCollectionView
ListCollectionView userListView = (ListCollectionView)CollectionViewSource.GetDefaultView(UserList);
userListView.CustomSort = new CustomUserComparer();
}
}
And this is my GroupDescriptionConverter
public class GroupDescriptionConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var groupId = (int)value;
var group = GroupController.Instance.GetGroup(groupId);
return group.Name;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
I want to bind the IsExpanded property on my expander on the ExpandGroup property of my group object.
public class Group
{
public int Id { get; set; }
public int OrderId { get; set; }
public string Name { get; set; }
public bool ExpandGroup { get; set; }
public bool NewLine { get; set; }
public Group()
{
}
}
A simple solution for this is to add a
private bool _isExpanded = true;
public bool IsExpanded
{
get { return _isExpanded; }
set { _isExpanded = value; }
}
property to your UserViewModel
and then do:
<Expander IsExpanded="{Binding Items[0].IsExpanded}">
on your template.
Simple and effective just like WPF should be

Why does my ConvertBack not getting called, WPF Converter & ValidationRule?

I'm trying to bind a textbox which can validate email addresses split by ',' or ';'.The objective is to have a checkbox, a textbox and a button on the page.If the checkbox is checked, then the user has to enter a valid email address or else the button has to be disabled.If the checdkbox is not clicked then the button has to be enabled.I'm going round in circles with this one, could you please help?
Please find my ViewModel structure below:
public class EmailValidatorViewModel : DependencyObject
{
public EmailValidatorViewModel()
{
OnOkCommand = new DelegateCommand<object>(vm => OnOk(), vm => CanEnable());
OtherRecipients = new List<string>();
}
private bool CanEnable()
{
return !IsChecked || HasOtherRecipients() ;
}
public static readonly DependencyProperty OtherRecipientsProperty =
DependencyProperty.Register("OtherRecipients", typeof(List<string>), typeof(EmailValidatorViewModel));
public List<string> OtherRecipients
{
get { return (List<string>)GetValue(OtherRecipientsProperty); }
set
{
SetValue(OtherRecipientsProperty, value);
}
}
public bool IsChecked { get; set; }
public void OnOk()
{
var count = OtherRecipients.Count;
}
public bool HasOtherRecipients()
{
return OtherRecipients.Count != 0;
}
public DelegateCommand<object> OnOkCommand { get; set; }
}
Also below is my XAML page:
<Window x:Class="EMailValidator.EMailValidatorWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:EMailValidator="clr-namespace:EMailValidator" Title="Window1" Height="300" Width="300">
<Window.Resources>
<Style x:Key="ToolTipBound" TargetType="TextBox">
<Setter Property="Foreground" Value="#333333" />
<Setter Property="MaxLength" Value="40" />
<Setter Property="Width" Value="392" />
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Text" Value=""/>
</Trigger>
</Style.Triggers>
</Style>
<EMailValidator:ListToStringConverter x:Key="ListToStringConverter" />
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="20"></RowDefinition>
<RowDefinition Height="20"></RowDefinition>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<CheckBox Margin="15,0,0,0" x:Name="OthersCheckbox" Grid.Row="2" Grid.Column="0" Unchecked="OnOthersCheckboxUnChecked" IsChecked="{Binding Path=IsChecked}">Others</CheckBox>
<TextBox Margin="5,0,5,0" x:Name="OtherRecipientsTextBox" Grid.Row="2" Grid.Column="1" IsEnabled="{Binding ElementName=OthersCheckbox, Path=IsChecked}" Style="{StaticResource ToolTipBound}">
<TextBox.Text>
<Binding Path="OtherRecipients" Mode="TwoWay" Converter="{StaticResource ListToStringConverter}" NotifyOnSourceUpdated="True">
<Binding.ValidationRules>
<EMailValidator:EmailValidationRule/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<Button x:Name="OkButton" Grid.Row="3" Grid.Column="0" Command="{Binding OnOkCommand}">Ok</Button>
</Grid>
</Window>
Also I do set my datacontext as below in the constructor of my xaml page.
public EMailValidatorWindow()
{
InitializeComponent();
DataContext = new EmailValidatorViewModel();
}
Here is my EMail Validator:
public class EmailValidationRule:ValidationRule
{
private const string EmailRegEx = #"\b[A-Z0-9._%-]+#[A-Z0-9.-]+\.[A-Z]{2,4}\b";
private readonly Regex regEx = new Regex(EmailRegEx,RegexOptions.IgnoreCase);
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
var inputAddresses = value as string;
if(inputAddresses == null)
return new ValidationResult(false,"An unspecified error occured while validating the input.Are you sure that you have entered a valid EMail address?");
var list = inputAddresses.Split(new[] {';',','});
var failures = list.Where(item => !regEx.Match(item).Success);
if(failures.Count() <= 0)
return new ValidationResult(true,null);
var getInvalidAddresses = string.Join(",", failures.ToArray());
return new ValidationResult(false,"The following E-mail addresses are not valid:"+getInvalidAddresses+". Are you sure that you have entered a valid address seperated by a semi-colon(;)?.");
}
...and my converter:
public class ListToStringConverter:IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var input = value as List<string>;
if (input.Count == 0)
return string.Empty;
var output = string.Join(";", input.ToArray());
return output;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
var input = value as string;
if (string.IsNullOrEmpty(input))
return new List<string>();
var list = input.Split(new[] { ';' });
return list;
}
}
Try setting the UpdateSourceTrigger to 'PropertyChanged' in your textbox binding.
<Binding UpdateSourceTrigger="PropertyChanged" Path="OtherRecipients" Mode="TwoWay" Converter="{StaticResource ListToStringConverter}" NotifyOnSourceUpdated="True">

Categories

Resources