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" />
Related
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?
I want to do something that sounds extremely simple, yet I find it very hard to achieve.
Let's assume I have some content which is bound to a slow-loading operation. For example, an observable list which is retrieved from a local SQL and takes a few seconds. While this is happening, I want to overlay the content presenter (e.g. a Groupbox) with a "Loading ..." text or any other 'please wait' type of content.
I quickly came to the conclusion that simply switching a boolean flag bound to the UI, before and after the operation doesn't work. The UI does not get refreshed until the entire operation completes. Maybe because the operation is CPU-intensive, I don't know.
I am now looking into Adorners, but very little information comes up with I search for it in the context of a 'busy indicator' overlay. There are just a few solutions on the Internet, from about 5 years ago and I can't get any of them to work.
The question:
As simple as it sounds - how to temporarily show something on the screen, while the View Model is working to update the bound data?
I quickly came to the conclusion that simply switching a boolean flag bound to the UI, before and after the operation doesn't work. The UI does not get refreshed until the entire operation completes. Maybe because the operation is CPU-intensive, I don't know.
Yes, it should work provided that you are actually executing the long-running operation on a background thread.
Please refer to the following simple example.
View:
<Window x:Class="WpfApplication2.Window1"
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:WpfApplication2"
mc:Ignorable="d"
xmlns:s="clr-namespace:System;assembly=mscorlib"
Title="Window1" Height="300" Width="300">
<Window.DataContext>
<local:Window1ViewModel />
</Window.DataContext>
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
</Window.Resources>
<Grid>
<TextBlock>Content...</TextBlock>
<Grid Background="Yellow" Visibility="{Binding IsLoading, Converter={StaticResource BooleanToVisibilityConverter}}">
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center">Loading...</TextBlock>
</Grid>
</Grid>
</Window>
View Model:
public class Window1ViewModel : INotifyPropertyChanged
{
public Window1ViewModel()
{
IsLoading = true;
//call the long running method on a background thread...
Task.Run(() => LongRunningMethod())
.ContinueWith(task =>
{
//and set the IsLoading property back to false back on the UI thread once the task has finished
IsLoading = false;
}, System.Threading.CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());
}
public void LongRunningMethod()
{
System.Threading.Thread.Sleep(5000);
}
private bool _isLoading;
public bool IsLoading
{
get { return _isLoading; }
set { _isLoading = value; NotifyPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Here is an example of how you can setup a View with a "Loading" display while the ViewModel\Model are working on some long task.
Window
<Window x:Class="Loading.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:Loading"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:VisibilityConverter x:Key="visibilityConverter" />
</Window.Resources>
<Window.DataContext>
<local:ViewModel x:Name="viewModel" />
</Window.DataContext>
<Grid>
<Button Content="Perform" VerticalAlignment="Bottom" HorizontalAlignment="Center" Width="100" Height="30" Command="{Binding HandleRequestCommand}" />
<Border Visibility="{Binding Path=IsLoading,Converter={StaticResource visibilityConverter}}" Background="#AAAAAAAA" Margin="5">
<TextBlock Text="Loading..." VerticalAlignment="Center" HorizontalAlignment="Center" />
</Border>
</Grid>
VisibilityConverter.cs (Simple helper converter)
class VisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (bool)value ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
ViewModel.cs
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool isLoading;
public ViewModel()
{
HandleRequestCommand = new Command(HandleRequest);
}
public bool IsLoading
{
get
{
return isLoading;
}
set
{
if (value != isLoading)
{
isLoading = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsLoading)));
}
}
}
public ICommand HandleRequestCommand
{
get;
}
public void HandleRequest()
{
IsLoading = true;
Task.Factory.StartNew(LongRunningOperation);
}
private void LongRunningOperation()
{
// *** INSERT LONG RUNNING OPERATION ***
Dispatcher.CurrentDispatcher.Invoke(() => IsLoading = false);
}
}
I have set a BindingGroup on my CustomControl and implemented a Validator function. Below is a code snippet from the XAML. My problem is that the validator never gets called. When I create a small sample program things are working. How could I trace the reason down?
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="350" Width="525">
<Window.BindingGroup>
<BindingGroup>
<BindingGroup.ValidationRules>
<local:DurationValidator/>
</BindingGroup.ValidationRules>
</BindingGroup>
</Window.BindingGroup>
<StackPanel>
<TextBox Text="{Binding SomeString, UpdateSourceTrigger=PropertyChanged}"></TextBox>
<Button>add</Button>
</StackPanel>
</Window>
Code behind:
namespace WpfApplication1
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
SomeString = "Some string";
}
private string _someString;
public string SomeString
{
get { return _someString; }
set
{
if (_someString == value) return;
_someString = value;
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("SomeString"));
}
}
}
public class DurationValidator : ValidationRule
{
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
BindingGroup bindingGroup = (BindingGroup)value;
return new ValidationResult(false, "Whatever");
}
}
}
Refer to the below code.
XAML
<StackPanel x:Name="stk">
<StackPanel.BindingGroup>
<BindingGroup Name="myBindingGroup">
<BindingGroup.ValidationRules>
<local:DurationValidator ValidatesOnTargetUpdated="True" />
</BindingGroup.ValidationRules>
</BindingGroup>
</StackPanel.BindingGroup>
<TextBox Text="{Binding BindingGroupName=myBindingGroup,UpdateSourceTrigger=PropertyChanged,Path=SomeString,Mode=TwoWay}"></TextBox>
<Button>add</Button>
</StackPanel>
C#
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
SomeString = "Some string";
}
private string _someString;
public string SomeString
{
get { return _someString; }
set
{
if (_someString == value) return;
_someString = value;
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("SomeString"));
this.stk.BindingGroup.CommitEdit();
}
}
}
public class DurationValidator : ValidationRule
{
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
BindingGroup bindingGroup = (BindingGroup)value;
return new ValidationResult(false, "Whatever");
}
}
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'm trying to bind a value down from a Window into a UserControl inside a UserControl. But, for some reason, the inner UserControl never even attempts to bind as far as I can tell.
MainWindow.xaml
<Window x:Class="PdfExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" xmlns:my="clr-namespace:PdfExample">
<Grid>
<my:FileSystemBrowser HorizontalAlignment="Left" x:Name="fileSystemBrowser1" VerticalAlignment="Top" Height="311" Width="417" RootPath="C:\TFS\AE.Web.ezHealthQuoter.Common\1_Dev\Shared\Pdfs" />
</Grid>
FileSystemBrowser.xaml
<UserControl x:Class="PdfExample.FileSystemBrowser"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300" xmlns:my="clr-namespace:PdfExample">
<DockPanel>
<my:FileSystemTree x:Name="fileSystemTree1" RootPath="{Binding Path=RootPath}" Width="150" />
<ListBox DockPanel.Dock="Right" />
</DockPanel>
FileSystemBrowser.xaml.cs
public partial class FileSystemBrowser : UserControl
{
#region Static Members
static FileSystemBrowser()
{
PropertyChangedCallback rootPathChangedCallback = new PropertyChangedCallback(OnRootPathChanged);
PropertyMetadata metaData = new PropertyMetadata(rootPathChangedCallback);
RootPathProperty = DependencyProperty.Register("RootPath", typeof(string), typeof(FileSystemBrowser), metaData);
}
static DependencyProperty RootPathProperty;
public static void OnRootPathChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as FileSystemBrowser).RootPath = e.NewValue as string;
}
#endregion
public string RootPath
{
get { return this.ViewModel.RootPath; }
set { this.ViewModel.RootPath = value; }
}
public FileSystemBrowserViewModel ViewModel
{
get;
protected set;
}
public FileSystemBrowser()
{
InitializeComponent();
this.ViewModel = new FileSystemBrowserViewModel();
this.DataContext = this.ViewModel;
}
}
public class FileSystemBrowserViewModel : INotifyPropertyChanged
{
private string _rootPath;
public string RootPath
{
get { return _rootPath; }
set { _rootPath = value; RaisePropertyChanged("RootPath"); }
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
FileSystemTree.xaml
<UserControl x:Class="PdfExample.FileSystemTree"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<DockPanel>
<TreeView SelectedValuePath="{Binding Path=SelectedValuePath, Mode=TwoWay}" HorizontalAlignment="Stretch" Name="treeView1" VerticalAlignment="Stretch" ItemsSource="{Binding RootFolder}" HorizontalContentAlignment="Left" VerticalContentAlignment="Top" Margin="0">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Folders}">
<TextBlock Text="{Binding FolderName}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</DockPanel>
FileSystemTree.xaml.cs
public partial class FileSystemTree : UserControl, INotifyPropertyChanged
{
#region Static Members
static DependencyProperty RootPathProperty;
static FileSystemTree()
{
PropertyChangedCallback rootPathChangedCallback = new PropertyChangedCallback(OnRootPathChanged);
PropertyMetadata metaData = new PropertyMetadata(rootPathChangedCallback);
RootPathProperty = DependencyProperty.Register("RootPath", typeof(string), typeof(FileSystemTree), metaData);
}
public static void OnRootPathChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as FileSystemTree).RootPath = e.NewValue as string;
}
#endregion
public string RootPath
{
get { return this.ViewModel.RootPath; }
set { this.ViewModel.RootPath = value; }
}
public FileSystemTreeViewModel ViewModel
{
get;
protected set;
}
public FileSystemTree()
{
InitializeComponent();
this.ViewModel = new FileSystemTreeViewModel();
this.DataContext = this.ViewModel;
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
public class FileSystemTreeViewModel : INotifyPropertyChanged
{
public IFolder[] RootFolder
{
get
{
if (RootPath != null)
return new IFolder[] { new FileSystemFolder(RootPath) };
return null;
}
}
private string _rootPath;
public string RootPath
{
get { return _rootPath; }
set
{
_rootPath = value;
RaisePropertyChanged("RootPath");
RaisePropertyChanged("RootFolder");
}
}
private string _selectedValuePath;
protected string SelectedValuePath
{
get { return _selectedValuePath; }
set { _selectedValuePath = value; }
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
I know that the tree works, because if I just put the tree inside MainWindow.xaml, it's fine. But for some reason, the RootPath value from MainWindow.xaml gets bound into FileSystemBrowser and stops there. It never makes it all the way down to FileSystemTree. What am I missing?
There is to few information to be certain, but I think the problem is the DataContext that is not set. Try relative bindings, this will probably help. In FileSystemBrowser.xaml change the binding as follows:
<my:FileSystemTree x:Name="fileSystemTree1"
RootPath="{Binding Path=RootPath,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=UserControl}}"
Width="150" />
Another possibility would be to set the UserControls this-reference to the DataContext. This should also help.