I have made a simple example of my issue. There is a window having its DataContext bount to MainWindowViewModel class. The window contains a Grid with a Button and a ProgressBar. The Button's command is bount to a ViewModel's RelayCommand that at first executes an increment of an integer property "Percent" from 0 to 100. The progressBar shows the progress just fine! The window contains a TaskBarItemInfo property, and the latter has two properties: a ProgressValue bount to a double property "Count", and a ProgressState bount to a TaskbarItemProgressState property "State", all properties in the in the MainWindowViewModel. The TaskbarItemInfo shows also just fine the increment from 0 to 1.
But in the second part of the initial method the progress becomes indeterminate for 5 seconds. While that shows just fine in the progressBar, it does not in the TaskBar. I have used both a Converter from boolean to TaskbarItemProgressState to the Path of the ProgressState property (IsIndeterminate), and a binding to a separate property State, as shown to the code below.
MainWindow.xaml
<Window x:Class="ProgressState.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:ProgressState"
mc:Ignorable="d"
Title="MainWindow" Height="200" Width="400">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Window.Resources>
<local:BoolToProgressStateConverter x:Key="boolToProgressStateConverter"/>
</Window.Resources>
<Window.TaskbarItemInfo>
<TaskbarItemInfo ProgressValue="{Binding Count, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}"
ProgressState="{Binding State, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}"/>
<!--ProgressState="{Binding IsIndeterminate, UpdateSourceTrigger=PropertyChanged, Mode=OneWay, Converter={StaticResource boolToProgressStateConverter}}"-->
</Window.TaskbarItemInfo>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Button Content="Start progress" Grid.Row="0" Height="30" Width="100"
Command="{Binding Start}" IsEnabled="{Binding IsEnabled, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
<ProgressBar Grid.Row="1" Height="20" Margin="10 0"
Value="{Binding Percent, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}"
IsIndeterminate="{Binding IsIndeterminate, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}"/>
</Grid>
</Window>
MainWindowViewModel.cs
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.CommandWpf;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Shell;
namespace ProgressState
{
public class MainWindowViewModel : ViewModelBase
{
private double count;
private bool isIndeterminate;
private int percent;
private bool isEnabled = true;
private TaskbarItemProgressState state;
public RelayCommand Start { get; set; }
public MainWindowViewModel()
{
Start = new RelayCommand(OnStart, CanStart);
}
public TaskbarItemProgressState State
{
get
{
if (IsIndeterminate)
{
state = TaskbarItemProgressState.Indeterminate;
}
else
{
state = TaskbarItemProgressState.Normal;
}
return state;
}
}
public bool IsEnabled
{
get
{
return isEnabled;
}
set
{
if (value != isEnabled)
{
isEnabled = value;
RaisePropertyChanged(nameof(IsEnabled));
}
}
}
public bool IsIndeterminate
{
get
{
return isIndeterminate;
}
set
{
if (value != isIndeterminate)
{
isIndeterminate = value;
RaisePropertyChanged(nameof(IsIndeterminate));
RaisePropertyChanged(nameof(State));
}
}
}
public int Percent
{
get
{
return percent;
}
set
{
if (value != percent)
{
percent = value;
RaisePropertyChanged(nameof(Percent));
RaisePropertyChanged(nameof(Count));
}
}
}
public double Count
{
get
{
count = Percent / 100d;
return count;
}
}
private bool CanStart()
{
return true;
}
private async void OnStart()
{
IsEnabled = false;
Percent = 0;
await Task.Run(() =>
{
while (Percent != 100)
{
Thread.Sleep(100);
Percent += 2;
}
IsIndeterminate = true;
Thread.Sleep(5000);
IsIndeterminate = false;
});
IsEnabled = true;
}
}
}
BoolToProgressStateConverter.cs
using System;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Shell;
namespace ProgressState
{
class BoolToProgressStateConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool boolean)
{
if (boolean == true)
{
return TaskbarItemProgressState.Indeterminate;
}
else if (boolean == false)
{
return TaskbarItemProgressState.Normal;
}
else
{
throw new NotImplementedException();
}
}
return TaskbarItemProgressState.Normal;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
Any ideas?
Related
I'm trying to bind the LineSeries Color to my viewmodel. It has no effect.
What is wrong?
The Datacontext is set in the MainWindow. That works because I get the data from Chartpoints.
<UserControl x:Class="MyLayerThicknessApp.UserControls.RefAufnehmenEdit"
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:oxy="clr-namespace:OxyPlot.Wpf;assembly=OxyPlot.Wpf"
xmlns:local="clr-namespace:MyLayerThicknessApp.UserControls"
mc:Ignorable="d"
d:DesignHeight="850" d:DesignWidth="1000">
<Grid>
<!--Chart-->
<oxy:Plot
LegendOrientation="Vertical"
LegendPlacement="Outside"
LegendPosition="RightTop" Grid.Row="1" IsEnabled="False">
<oxy:Plot.Axes>
<oxy:LinearAxis Position="Bottom" Minimum="400" Maximum="850" MajorGridlineStyle="None" MinorGridlineStyle="None" Title="Wellenlänge [nm]" />
<oxy:LinearAxis Position="Left" Minimum="0" Maximum="4000" Title="Intensität" MajorStep="1000" MajorGridlineStyle="Automatic" MinorGridlineStyle="None"/>
</oxy:Plot.Axes>
<oxy:Plot.Series>
<oxy:LineSeries
DataFieldY="YValue" DataFieldX="XValue" ItemsSource="{Binding ChartPoints}" Color="{Binding LinieColor}" LineStyle="Solid" />
</oxy:Plot.Series>
</oxy:Plot>
</Grid>
</UserControl>
// Mainwindow
<Window x:Class="MyLayerThicknessApp.Views.RecipesView"
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:MyLayerThicknessApp.Views"
xmlns:vm="clr-namespace:MyLayerThicknessApp.ViewModels"
xmlns:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
xmlns:oxy="clr-namespace:OxyPlot.Wpf;assembly=OxyPlot.Wpf"
xmlns:MyControls="clr-namespace:MyLayerThicknessApp.UserControls"
mc:Ignorable="d"
Title="RezepteWindow" Height="850" Width="1200" WindowStartupLocation="CenterOwner" Style="{StaticResource Window}" IsEnabled="{Binding MainWindowIsEnable}" >
<Window.DataContext>
<vm:RecipesViewModel/>
</Window.DataContext>
<Grid Background="Transparent">
<MyControls:RefAufnehmenEdit Visibility="{Binding VisEditRefAufnehmen, Converter={StaticResource BooleanToVisibilityConverter}}" />
</Grid>
</Window>
My ViewModel. I also try with Color and Brushes. Nothing works.
The INotifyPropertyChanged is in the MainViewMode. This works for sure.
public class RecipesViewModel : MainViewModel
{
private bool visEditRefAufnehmen;
public bool VisEditRefAufnehmen
{
get { return visEditRefAufnehmen; }
set
{
if (visEditRefAufnehmen != value)
{
visEditRefAufnehmen = value;
OnPropertyChanged("VisEditRefAufnehmen"); // To notify when the property is changed
}
}
}
private OxyColor linieColor = OxyColor.Parse("255,0,0");
public OxyColor LinieColor
{
get { return linieColor; }
set
{
if (linieColor != value)
{
linieColor = value;
OnPropertyChanged("LinieColor"); // To notify when the property is changed
}
}
}
private List<DataPoint> currentchartPoints;
public List<DataPoint> CurrentchartPoints
{
get { return currentchartPoints; }
set
{
if (value != currentchartPoints)
{
currentchartPoints = value;
OnPropertyChanged("CurrentchartPoints");
}
}
}
private void spektrumAufnehmen()
{
if (spektrometer == null)
{
spektrometer = MySpektrometer.getInstanz();
spektrometer.SetINtegrationTime(10);
}
Task.Run(() =>
{
while (VisEditRefAufnehmen)
{
double[] intensity = MySpektrometer.Wrapper.getSpectrum(0);
double[] wave = MySpektrometer.Wrapper.getWavelengths(0);
wave = MyFilter.convertBigArrayToSmall(wave, intensity, Brechungsindex.WAVELENGTHAREA);
ChartPoints = DoubleArrayToChartPoints(wave);
}
});
}
}
// INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
The property LineSeries.Color expects a type of System.Windows.Media.Color, where as, the the property in your ViewModel, LinieColor, is of type OxyColor.
From OxyPlot Source
public static readonly DependencyProperty ColorProperty = DependencyProperty.Register(
"Color", typeof(Color), typeof(Series), new PropertyMetadata(MoreColors.Automatic, AppearanceChanged));
From your ViewModel
public OxyColor LinieColor
You could resolve the binding issue by using a converter that converts OxyColor to Color. A simplest converter for the purpose could be following.
public class OxyColorToColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is OxyColor color)
return color.ToColor();
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
And in your XAML
<oxy:LineSeries DataFieldY="YValue" DataFieldX="XValue" ItemsSource="{Binding ChartPoints}" Color="{Binding LinieColor,Converter={StaticResource OxyColorToColorConverter}}" LineStyle="Solid" />
I am writing simple application,
The UI has two textboxes, for Username & Password and button to submit the information.
I wanted to use routed commands instead of buttonclick event.
Username should contain alphanumeric characters only, if user enter any other special characters, it should display a text saying invalid characters entered.
so I wanted to bind the visibility and content of that textblock based on the validation done on Username textbox field.
can any one help me on how to achieve this?
Below is code I have made but it is not working as expected. can any one help me where I am doing wrong?
Below my mainWindow.xaml
using System.Windows;
using System.Windows.Input;
using System.ComponentModel;
namespace ExcelUtility
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
ViewModel viewModelObj = new ViewModel();
public MainWindow()
{
InitializeComponent();
}
void navigatePageExecuted(object target, ExecutedRoutedEventArgs e)
{
SubmitUserDetails(txtUserName.Text + ";" + txtPassword);
}
void navigatePageCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
if (!string.IsNullOrWhiteSpace(txtUserName.Text))
{
viewModelObj.Username = txtUserName.Text;
}
e.CanExecute = viewModelObj.VaidUserName; }
private void SubmitUserDetails(string credentials)
{
this.Cursor = Cursors.Wait;
prgValidate.Visibility = Visibility.Visible;
MainGrid.IsEnabled = false;
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += worker_DoWork;
worker.RunWorkerCompleted += worker_RunWorkerCompleted;
worker.RunWorkerAsync(credentials);
}
void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
prgValidate.Visibility = Visibility.Collapsed;
string Result = (string)e.Result;
MessageBox.Show(Result); //Here I need to call some other functions based on return value for simplicity i have changed
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
string[] credentials = e.Argument.ToString().Split(';');
e.Result = viewModelObj.validateCredentials(credentials[0], credentials[1]);
}
}
}
This is my xaml
<Window x:Class="ExcelUtility.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ExcelUtility"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:BoolToVisibleOrHidden x:Key="BoolToVisibilityConverter"/>
</Window.Resources>
<Window.CommandBindings>
<CommandBinding Command="{x:Static local:CommandsLibrary.navigatePageCommand}" Executed="navigatePageExecuted" CanExecute="navigatePageCanExecute"/>
</Window.CommandBindings>
<Grid Name="MainGrid">
<TextBlock Height="23" HorizontalAlignment="Left" Margin="40,44,0,0" Name="tbUserName" Text="Username" VerticalAlignment="Top" />
<TextBox Height="23" HorizontalAlignment="Left" Margin="136,42,0,0" Name="txtUserName" VerticalAlignment="Top" Width="163" Text="{Binding Username, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Height="23" HorizontalAlignment="Left" Margin="138,19,0,0" Name="tbNotify" Text="{Binding Notification, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top" Width="161" Visibility="{Binding NotVaidUserName,Converter={StaticResource BoolToVisibilityConverter}}" />
<TextBox Height="23" HorizontalAlignment="Left" Margin="138,98,0,0" Name="txtPassword" VerticalAlignment="Top" Width="161" />
<TextBlock Height="23" HorizontalAlignment="Left" Margin="44,107,0,0" Name="tbPassword" Text="Password" VerticalAlignment="Top" Width="65" />
<Button Command="{x:Static local:CommandsLibrary.navigatePageCommand}" Content="Submit" Height="23" HorizontalAlignment="Left" Margin="172,167,0,0" Name="btnSubmit" VerticalAlignment="Top" Width="109" />
<ProgressBar Height="24" IsIndeterminate="True" Visibility="Collapsed" HorizontalAlignment="Left" Margin="52,232,0,0" Name="prgValidate" VerticalAlignment="Top" Width="257" />
</Grid>
This is my viewModel
using System;
using System.Text.RegularExpressions;
using System.ComponentModel;
using System.Windows.Data;
using System.Windows;
namespace ExcelUtility
{
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool _notVaidUserName;
public bool NotVaidUserName
{
get { return _notVaidUserName; }
set
{
_notVaidUserName = value;
RaisePropertyChanged("NotVaidUserName");
}
}
private string notification;
public string Notification
{
get
{
return notification;
}
set
{
if (notification != value)
{
notification = value;
RaisePropertyChanged("Notification");
}
}
}
private string username;
public string Username
{
get
{
return username;
}
set
{
if (username != value)
{
username = value;
NotVaidUserName = VaidateUserName(username);
RaisePropertyChanged("Username");
}
}
}
public bool VaidateUserName(string strUsername)
{
bool bValidUserName = false;
if (!string.IsNullOrWhiteSpace(strUsername))
{
if (new Regex(#"^[a-zA-Z0-9]*$").IsMatch(strUsername))
{
bValidUserName = true;
if (strUsername.Length > 7)
{
Notification = "Max allowed key length is 6";
bValidUserName = false;
}
}
else
{
Notification = "No special characters allowed";
}
}
return bValidUserName;
}
public string validateCredentials(string Username, string Password)
{
return "Valid Credentials";
}
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
class BoolToVisibleOrHidden : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
object returnvalue = new object();
returnvalue = (bool)value ? Visibility.Visible : parameter != null ? Visibility.Collapsed : Visibility.Hidden;
return returnvalue;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (Visibility)value == Visibility.Visible;
}
}
}
Many thanks in advance.
Durga
The reason your code is not working is that you have not set the data context for view. I like to set the data context in the xaml as it will give you auto-complete in VS for your binding instruction. On the root node, add the attribute
DataContext="{Binding RelativeSource={RelativeSource Self}}"
This will set the window itself as your data context, allowing your command to work. However, the binding expressions on the textboxes will fail. You have split your ViewModel logic over the view and the ViewModel.
I would move you code completely into the ViewModel and use
DataContext="{Binding RelativeSource={RelativeSource Self}, Path=ViewModel}"
as the data context. Then the only code you have in the code behind of the view would be something along the lines of
namespace ExcelUtility
{
public partial class MainWindow : Window
{
private ViewModel viewModel;
public MainWindow()
{
InitializeComponent();
}
public ViewModel ViewModel { get { return viewModel ?? (viewModel = new ViewModel()); } }
}
}
Once your bindings are working, you will not have to set the UserName or Password (as you do in the navigatePageCanExecute and navigatePageExecuted methods) in the view model as the binding will set it for you.
I am not sure about your CommandsLibrary. You didn't include it in the example.
This should give you a start to figure out the rest.
I hope it helps
In my View Model (VM) I have an ObservableCollection of items. In my view I am binding to the collection. I have created a few user controls that have a dependency property that i am binding to called STCode. So for example a "Tag" object will have a "Name" property of type String and a "value" property of type integer.
In my ViewModel
Constructor
Tags.Add(new Tag("Tag1",111));
Tags.Add(new Tag("Tag2",222));
Tags.Add(new Tag("Tag3",333));
Tags.Add(new Tag("Tag4",444));
public ObservableCollection<Tag> Tags
{
get
{
return _TagList;
}
set
{
if (value != _TagList)
{
_TagList = value;
}
}
}
In my View
<my:UserControl1 x:Name="control1" Margin="12,89,0,0" HorizontalAlignment="Left" Width="257" Height="249" VerticalAlignment="Top" STCode="{Binding Path=Value}"/>
This will bind to the First items value property in the ObservableCollection (Showing "Tag1" value). Is there anyway that I can get a specific "tag" object from the observableCollection by specifying the string Name property? So basically if I had 3 instances of my usercontrol in the view, on each control I would like to specify the "Name" property of the Tag object as a string in XAML, and in return bind that specific control to that specific tags integer "Value" property?
I hope this makes sense
Model
public class Tag : ModelBase
{
private int _value;
public string Tagname { get; set; }
public int Value
{
get
{
return _value;
}
set
{
_value = value;
NotifyPropertyChanged("Value");
}
}
}
ModelBase
public class ModelBase :INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Since your UserControls are bound to the Collection itself and not the Item on the collection (the converter does this job internally) you must call PropertyChanged on the whole Collection when you want to refresh the bindings on your usercontrols.
Edit: Full solution
ViewModel:
public class MainWindowViewModel : INotifyPropertyChanged
{
public ObservableCollection<Tag> Tags { get; private set; }
public MainWindowViewModel()
{
Tags = new ObservableCollection<Tag>();
Tags.Add(new Tag("Tag1", 111));
Tags.Add(new Tag("Tag2", 222));
Tags.Add(new Tag("Tag3", 333));
Tags.Add(new Tag("Tag4", 444));
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
public void ChangeRandomTag()
{
var rand = new Random();
var tag = Tags[rand.Next(0, Tags.Count - 1)];
tag.Value = rand.Next(0, 1000);
OnPropertyChanged("Tags");
}
}
View 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:wpfApplication1="clr-namespace:WpfApplication1"
Title="MainWindow"
Width="525"
Height="350">
<Window.Resources>
<wpfApplication1:MyConverter x:Key="MyConverter" />
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ListBox ItemsSource="{Binding Tags}">
<ListBox.ItemTemplate>
<DataTemplate>
<Border Margin="1"
BorderBrush="Black"
BorderThickness="1"
CornerRadius="2">
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Value}" />
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel Grid.Column="1" Orientation="Vertical">
<Button x:Name="buttonChangeRandomTag"
Click="ButtonChangeRandomTag_OnClick"
Content="Change Random Tag Value" />
<TextBlock Text="{Binding Tags, Converter={StaticResource MyConverter}, ConverterParameter=Tag1}" />
<TextBlock Text="{Binding Tags, Converter={StaticResource MyConverter}, ConverterParameter=Tag2}" />
<TextBlock Text="{Binding Tags, Converter={StaticResource MyConverter}, ConverterParameter=Tag3}" />
<TextBlock Text="{Binding Tags, Converter={StaticResource MyConverter}, ConverterParameter=Tag4}" />
</StackPanel>
</Grid>
View Code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
DataContext = new MainWindowViewModel();
InitializeComponent();
}
private void ButtonChangeRandomTag_OnClick(object sender, RoutedEventArgs e)
{
(DataContext as MainWindowViewModel).ChangeRandomTag();
}
}
Converter:
[ValueConversion(typeof(ObservableCollection<Tag>), typeof(int))]
public class MyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var collection = value as ObservableCollection<Tag>;
var key = parameter as string;
if (collection == null || parameter == null)
return 0;
var result = collection.FirstOrDefault(item => item.Name.Equals(key));
if (result == null)
return 0;
return result.Value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Tag Class:
public class Tag : INotifyPropertyChanged
{
private string name;
private int value;
public string Name
{
get { return name; }
set
{
if (value == name) return;
name = value;
OnPropertyChanged("Name");
}
}
public int Value
{
get { return value; }
set
{
if (value == this.value) return;
this.value = value;
OnPropertyChanged("Value");
}
}
public Tag(string name, int value)
{
Value = value;
Name = name;
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
You could use a ValueConverter to do that for you:
[ValueConversion(typeof(string), typeof(string))]
public class StringToTagPropertyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null || value != typeof(ObservableCollection<Tag>)) return
DependencyProperty.UnsetValue;
if (parameter as string == null) return DependencyProperty.UnsetValue;
ObservableCollection<Tag> tagObject = (ObservableCollection<Tag>)value;
string returnValue = tagObject.Where(t => t.Name.ToLower() ==
parameter.ToString().ToLower()).FirstOrDefault();
return returnValue ?? DependencyProperty.UnsetValue;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return DependencyProperty.UnsetValue;
}
}
You would use it like this:
<my:UserControl1 x:Name="control1" Margin="12,89,0,0" HorizontalAlignment="Left"
Width="257" Height="249" VerticalAlignment="Top" STCode="{Binding Tags,
Converter={StaticResource StringToTagPropertyConverter},
ConverterParameter="Name"}" />
By changing the value of the ConverterParameter, you can get the ValueConverter to return different properties of your 'tag object'. I am assuming that you know how to add a value converter in XAML.
I have a List of 'Rule' classes that is the source of a DataGrid. In this example I have one of the columns which is a DataGridTemplateColumn that is bound to the 'Verified' dependency property.
The problem I am having is that I have a VerifyColorConverter where I wish to pass in the ENTIRE 'Rule' object of the selected row so I can examine the Rule instance and return an appropriate brush. I do this in when setting the background of the Border (see code below - Background="{Binding Converter={StaticResource convVerify}}")
<DataGridTemplateColumn Header="Verified" Width="150">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Border Background="{Binding Converter={StaticResource convVerify}}"
CornerRadius="4" Height="17" Margin="2,0,2,0" VerticalAlignment="Center" >
<Grid>
<TextBlock Foreground="Yellow" Text="{Binding Path=Verified, Mode=OneWay}" TextAlignment="Center" VerticalAlignment="Center"
FontSize="11" FontWeight="Bold" />
</Grid>
</Border>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
This all works well when I set the source on the DataGrid but when the underlying 'Rule' object is altered the converter is not called upon so the brush stays the same. How can I force this to be updated when I alter some of the properties of the 'Rule' instance?
Any help appreciated!
Converter looks roughly like this:
[ValueConversion(typeof(CRule), typeof(SolidColorBrush))]
public class VerifyColorConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
CRule rule = value as CRule;
Color clr = Colors.Red;
int count = 0;
int verified = 0;
if (rule != null)
{
count = rule.TotalCount;
verified = rule.NoOfVerified;
}
if (count == 0) clr = Colors.Transparent;
else if (verified == 0) clr = (Color)ColorConverter.ConvertFromString("#FFD12626");
else if (verified < count) clr = (Color)ColorConverter.ConvertFromString("#FF905132");
else clr = (Color)ColorConverter.ConvertFromString("#FF568D3F");
SolidColorBrush brush = new SolidColorBrush(clr);
return brush;
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
EDIT
This is part of Rule class:
/// <summary>
/// Compliance Restriction (Rule)
/// </summary>
public class Rule : BindElement
{
public CMode Mode { get; private set; }
public int RuleID { get; private set; }
public string RuleDescription { get; private set; }
private int _NoOfVerified = 0;
private int _TotalCount = 0;
public int NoOfVerified
{
get { return _NoOfVerified; }
set { _NoOfVerified = value; RaiseChanged("Progress"); RaiseChanged("Verified"); }
}
public int TotalCount
{
get { return _TotalCount; }
set { _TotalCount = value; RaiseChanged("Progress"); RaiseChanged("Verified"); }
}
public string Verified
{
get
{
if (TotalCount == 0) return "Nothing to verify";
return string.Format("Verified {0} out of {1}", NoOfVerified, TotalCount);
}
}
You could try using a MultiValueConverter instead of a regular Converter, and pass it whatever Rule Properties you need, or you can raise a CollectionChanged event when a property on the Rule gets changed. I usually prefer not to do this since I don't know how this affects performance, but it's an option.
Using a MultiConverter
public object Convert(object[] values, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
Color clr = Colors.Red;
int count = 0;
int verified = 0;
if (values.Count >= 2
&& int.TryParse(count, values[0].ToString())
&& int.TryParse(verfieid, values[1].ToString()))
{
if (count == 0) clr = Colors.Transparent;
else if (verified == 0) clr = (Color)ColorConverter.ConvertFromString("#FFD12626");
else if (verified < count) clr = (Color)ColorConverter.ConvertFromString("#FF905132");
else clr = (Color)ColorConverter.ConvertFromString("#FF568D3F");
}
SolidColorBrush brush = new SolidColorBrush(clr);
return brush;
}
XAML for MultiConverter:
<Border.Background>
<MultiBinding Converter="{StaticResource convVerify}">
<Binding Value="{Binding TotalCount}" />
<Binding Value="{Binding NoOfVerified}" />
</MultiBinding>
</Border.Background>
Using Property Change Events
RulesCollection.CollectionChanged += RulesCollection_Changed;
void RulesCollection_Changed(object sender, CollectionChangedEventArgs e)
{
if (e.NewItems != null)
foreach(Rule rule in e.NewItems) // May need a cast here
rule.PropertyChanged += Rule_PropertyChanged;
if (e.OldItems != null)
foreach(Rule rule in e.OldItems) // May need a cast here
rule.PropertyChanged -= Rule_PropertyChanged;
}
void Rule_PropertyChanged()
{
RaisePropertyChanged("RulesCollection");
}
OK - I found a way around this - what I have done is expose the object as a property and then call the OnPropertyChanged for this property whenever anything changes. This is picked up properly by the Bind object and handed over to the Converter whenever a property changes!!
[Serializable()]
public class BindElement : INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
[field: NonSerialized]
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
PropertyChanged(this, e);
}
public void RaiseChanged(string s)
{
OnPropertyChanged(new PropertyChangedEventArgs(s));
}
#endregion
}
public class BindElement2 : BindElement
{
public void RaiseChanged(string s)
{
base.RaiseChanged(s);
NudgeMyself();
}
public void NudgeMyself()
{
base.RaiseChanged("Myself");
}
}
/// <summary>
/// Compliance Restriction (Rule)
/// </summary>
public class CRule : BindElement2
{
public CMode Mode { get; private set; }
public int RuleID { get; private set; }
public string RuleDescription { get; private set; }
private int _NoOfVerified = 0;
private int _TotalCount = 0;
public int NoOfVerified
{
get { return _NoOfVerified; }
set { _NoOfVerified = value; RaiseChanged("Progress"); RaiseChanged("Verified"); }
}
public int TotalCount
{
get { return _TotalCount; }
set { _TotalCount = value; RaiseChanged("Progress"); RaiseChanged("Verified"); }
}
public string Verified
{
get
{
if (TotalCount == 0) return "Nothing to verify";
return string.Format("Verified {0} out of {1}", NoOfVerified, TotalCount);
}
}
public CRule Myself
{
get { return this; }
}
and other classes can derived from BindElement2 and do the same: (create a property Myself that exposes the instance itself)
public class CTradeRule : BindElement2
{
public CRule Rule { get; set; }
public bool IsLocked { get; set; } // if true this should prevent a Reason from being given
public bool IsVerified { get { return Reason.Length > 0; } }
private string _Reason = ""; // ** no reason **
public string Reason
{
get { return _Reason; }
set { _Reason = value; RaiseChanged("Reason"); }
}
public int Progress
{
get { return (IsVerified ? 1 : 0); }
}
public override string ToString()
{
return string.Format("Rule: {0}, Reason: {1}", Rule.RuleID, _Reason);
}
public CTradeRule Myself
{
get { return this; }
}
}
Now in the xaml I can do this: (note the Binding Path=Myself) which then ensures the entire object is send to the converter WHENEVER any property changes!!
<DataGridTemplateColumn Header="Verified" Width="150">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Border Background="{Binding Path=Myself, Converter={StaticResource convVerify}}"
CornerRadius="4" Height="17" Margin="2,0,2,0" VerticalAlignment="Center" >
<Grid>
<TextBlock Foreground="Yellow" Text="{Binding Path=Verified, Mode=OneWay}" TextAlignment="Center" VerticalAlignment="Center"
FontSize="11" FontWeight="Bold" />
</Grid>
</Border>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
Is there a way to bind to the ItemIndex from within the ItemTemplate of an ItemsControl?
For example:
<ItemsControl ItemsSource="{Binding Path=ItemList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=ThisItemsIndex}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
If you're not using any type of alternating row styles you might be able to hijack the AlternationIndex for this. Set AlternationCount on your ItemsControl to something greater than the max possible count of your items and then use
Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=(ItemsControl.AlternationIndex)}"
Edit: As bradgonesurfing pointed out in comments, this is not recommended if you're using virtualization, as it will only index the items that are generated and not the entire list.
Here is a method I used to add a bindable index on a collection item. I basically wrap my item in a container that has an index, and have a custom ObservableCollection that accepts the wrapper.
Note that MoveItem is not overridden, but would have to be for a complete implementation.
public class IndexedItemContainerCollection<T> : ObservableCollection<IndexedItemContainer<T>>
{
public IndexedItemContainerCollection()
{
}
public IndexedItemContainerCollection(IEnumerable<IndexedItemContainer<T>> collection)
: base(collection)
{
var index = 0;
foreach (var item in this)
{
item.Index = index;
}
}
protected override void InsertItem(int index, IndexedItemContainer<T> item)
{
item.Index = index;
base.InsertItem(index, item);
foreach (var indexedItem in this.Where(x=>x.Index > index))
{
indexedItem.Index++;
}
}
protected override void RemoveItem(int index)
{
base.RemoveItem(index);
foreach (var indexedItem in this.Where(x => x.Index > index))
{
indexedItem.Index--;
}
}
}
public class IndexedItemContainer<T>
{
public int Index { get; set; }
public T Item { get; set; }
}
I then extend my wrapper class to get a bindable property that I have control over how the index is displayed:
public class NamedIndexedItemContainer<T> : IndexedItemContainer<T>
{
public string Name
{
get { return string.Format("Item #{0}", Index + 1); }
}
}
Sample Usage
XAML:
<ComboBox ItemsSource="{Binding ItemList}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Code:
private IndexedItemContainerCollection<MyItem> _itemList;
public IndexedItemContainerCollection<MyItem> ItemList
{
get { return _itemList; }
set { _itemList= value; OnPropertyChanged(); }
}
ItemList = new IndexedItemContainerCollection<MyItem>();
var newItem = new NamedIndexedItemContainer<MyItem>() { Item = new MyItem() { ... } };
ItemList.Add(newItem);
Of course, any binding with the actual MyItem instance would have to go through the IndexedItemContainer's Item property.
For the record, there is another way to accomplish this: using custom Converter. A little bit more complicated, but you do not have to worry about AlternationCount/Index.
public sealed class ArrayWrapperConverter : IValueConverter
{
private static readonly Type ArrayWrappingHelperType = typeof(ArrayWrappingHelper<>);
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null)
{
return null;
}
Type valueType = value.GetType();
if (!valueType.IsArray)
{
return DependencyProperty.UnsetValue;
}
Type elementType = valueType.GetElementType();
Type specificType = ArrayWrappingHelperType.MakeGenericType(elementType);
IEnumerable wrappingHelper = (IEnumerable) Activator.CreateInstance(specificType, value);
return wrappingHelper;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
internal class ArrayWrappingHelper<TValue> : IEnumerable
{
private readonly TValue[] _array;
public ArrayWrappingHelper(object array)
{
_array = (TValue[]) array;
}
public IEnumerator GetEnumerator()
{
return _array.Select((item, index) => new ArrayItemWrapper<TValue>(_array, index)).GetEnumerator();
}
}
public class ArrayItemWrapper<TValue>
{
private readonly TValue[] _array;
private readonly int _index;
public int Index
{
get { return _index; }
}
public TValue Value
{
get { return _array[_index]; }
set { _array[_index] = value; }
}
public ArrayItemWrapper(TValue[] array, int index)
{
_array = array;
_index = index;
}
}
Sample usage:
<Window x:Class="WpfArrayBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:c="clr-namespace:WpfArrayBinding.Converters"
xmlns:s="clr-namespace:System;assembly=mscorlib"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<ResourceDictionary>
<c:ArrayWrapperConverter x:Key="ArrayWrapperConverter" />
<x:Array Type="{x:Type s:String}" x:Key="MyArray">
<s:String>Foo</s:String>
<s:String>Bar</s:String>
<s:String>Baz</s:String>
</x:Array>
</ResourceDictionary>
</Window.Resources>
<ItemsControl ItemsSource="{Binding Source={StaticResource MyArray}, Converter={StaticResource ArrayWrapperConverter}}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Index}" />
<TextBox Text="{Binding Value}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Window>