I have a boolean property (that does called INotifyPropertyChanged in the setter) that is bound to a button.IsEnabled property in my XAML. Currently I'm using a TwoWay binding, but this is causing problems and I only need a OneWay binding. My problem is that the converter I'm using doesn't get called beyond the first time the program starts up. I've put breakpoints in the setter and it gets called loads, but the Convert() method doesn't get called at all. Why is this?
Some code:
public bool IsSaving
{
get
{
return _isSaving;
}
set
{
_isSaving = value;
NotifyOfPropertyChange(() => IsSaving);
}
}
and the XAML:
IsEnabled="{Binding Path=IsSaving, Mode=OneWay, Converter={StaticResource booleanToNotEnabledConverter}}"
The converter really just returns !(bool)value so the button gets disabled when IsSaving is true.
Some changes at runtime might cause the binding to break (since you bind to the DataContext + a relative path), if you use Visual Studio make sure to check the Output-window for any binding errors.
Edit: Since it has not been noted: That is a stardard binding and there is nothing wrong with the posted code, the problem has to be caused by the context.
Here is the code I used and this works:
Converter:
using System.Windows.Data;
using System;
namespace SilverlightApplication1
{
public class BooleanToNotEnabledConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return !(bool)value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
XAML:
<UserControl x:Class="SilverlightApplication1.MainPage"
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:SilverlightApplication1"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<UserControl.Resources>
<local:BooleanToNotEnabledConverter x:Key="booleanToNotEnabledConverter" />
</UserControl.Resources>
<StackPanel Margin="100">
<Button Content="Flip"
Click="Button_Click" />
<TextBlock Text="{Binding IsSaving}"
Height="20" />
<Button IsEnabled="{Binding IsSaving, Mode=OneWay, Converter={StaticResource booleanToNotEnabledConverter}}"
Content="Some Button" />
</StackPanel>
</UserControl>
Code behind:
using System.Windows.Controls;
using System.Windows;
using System.ComponentModel;
namespace SilverlightApplication1
{
public partial class MainPage : UserControl
{
private Data _data;
public MainPage()
{
InitializeComponent();
_data = new Data { IsSaving = true };
this.DataContext = _data;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
_data.IsSaving = !_data.IsSaving;
}
}
public class Data : INotifyPropertyChanged
{
#region IsSaving Property
private bool _isSaving;
public bool IsSaving
{
get
{
return _isSaving;
}
set
{
if (_isSaving != value)
{
_isSaving = value;
OnPropertyChanged("IsSaving");
}
}
}
#endregion
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
var p = PropertyChanged;
if (p != null)
{
p(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Are you sure you invoke the PropertyChanged event handler with the correct string?
PropertyChanged.Invoke(this, new PropertyChangedEventArgs("IsSaving"));
Related
I have a unique situation. In one class, I have an inner class that acts as pretty much just a "display" class. In the outer class there is a method called GetDisplayObject that returns a type of the inner class.
I'm trying to bind to a datagrid with the outer class, but by using a converter I'd like to get the correct display. This way I won't have to change a bunch of code in our application and just add a few lines in a couple .xaml files.
I made a little test app that pretty much sums up my problem at it's most basic level. Ideally I'd like to solve the problem by using a converter and returning values only as a display, that way when i'm using the SelectedItem I won't have to change a ton of code that is depending on that certain type(In this case would be the DataObject type).
So here is the objects i'm stuck dealing with
namespace TestApp
{
public class DataObject
{
public class DataObjectDisplay
{
public string ObjectDisplay { get; set; }
}
// props
public int Id { get; set; }
public object Object1 { get; set; }
public object Object2 { get; set; }
// method for getting the display
public DataObjectDisplay GetDisplayObject()
{
DataObjectDisplay display = new DataObjectDisplay();
// logic for determining which object should be displayed
if(Object1 == null)
{
display.ObjectDisplay = "Object1";
}
else
{
display.ObjectDisplay = "Object2";
}
return display;
}
}
}
Here is the Code Behind of my xaml
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
namespace TestApp
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
this.DataContext = this;
InitializeComponent();
this.DataObjectCollection = new ObservableCollection<DataObject>();
this.DataObjectCollection.Add(new DataObject() { Id = 1, Object1 = "this", Object2 = "that" });
this.DataObjectCollection.Add(new DataObject() { Id = 1, Object2 = "that" });
this.SelectedItem = new DataObject();
}
private ObservableCollection<DataObject> dataObjectCollection;
private DataObject selectedItem;
public ObservableCollection<DataObject> DataObjectCollection
{
get { return this.dataObjectCollection; }
set
{
dataObjectCollection = value;
OnNotifyPropertyChanged("DataObjectCollection");
}
}
public DataObject SelectedItem
{
get { return this.selectedItem; }
set
{
selectedItem = value;
OnNotifyPropertyChanged("SelectedItem");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnNotifyPropertyChanged(string property = "")
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
}
Here is the xaml(This is something like what i'd want to do, using the itemtemplate or something similar, and then a converter to call this GetDisplay function)
<Window x:Class="TestApp.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:TestApp"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:DataObjectToDisplayDataObjectConverter x:Key="ToDisplayConverter"/>
</Window.Resources>
<StackPanel>
<DataGrid ItemsSource="{Binding DataObjectCollection}" SelectedItem="{Binding SelectedItem}" MinHeight="200">
<DataGrid.ItemTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding Converter={StaticResource ToDisplayConverter}}"/>
</DataTemplate>
</DataGrid.ItemTemplate>
</DataGrid>
</StackPanel>
</Window>
And Finally the converter
using System;
using System.Globalization;
using System.Windows.Data;
namespace TestApp
{
public class DataObjectToDisplayDataObjectConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null && value.GetType() == typeof(DataObject))
{
DataObject dataObj = (DataObject)value;
dataObj.GetDisplayObject();
return dataObj;
}
return "Invalid Value";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
I am open to suggestions, but this is a small example. In our actual application changing one thing will most likely cascade into a huge ordeal.
If I were you I would have change close coupling of view with ViewModel, Like creating new MainWindowViewModel class & having all properties in it.
Another thing I see GetDisplayObject method, what's the need of calling such a method from converter.
You can re-factor this code & put in the converter something like this.
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
DataObject dataObj = value as DataObject;
if (value == null)
{
return "Invalid Value";
}
if (dataObj.Object1 == null)
{
return "Object1";
}
return "Object2";
}
Luckily since it was a matter of if one object was null and it's related strings were empty, I was able to use Priority Binding with a converter to return DependencyProperty.UnsetValue and force it to use the next binding. I think this was the best solution for the situation.
So the xaml ended up looking like this.
<DataGrid ItemsSource="{Binding DataObjectCollection}" SelectedItem="{Binding SelectedItem}" MinHeight="200">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<PriorityBinding>
<Binding Path="Object1" Converter="{StaticResource EmptyStringToDependencyPropertyUnset}"/>
<Binding Path="Object2"/>
</PriorityBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
And the Converter ended up like this
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if(value == null || value as string == string.Empty)
{
return DependencyProperty.UnsetValue;
}
return value;
}
I've searched this question a lot. But, it seems no solutions are working for me. I don't get any errors but I've added a breakpoint to my IValueConverter. The breakpoint does not ever get triggered. Why is it not using my converter? All I want to do is use view model strategy for visibility binding of a UI element (in this case a checkbox). Any help is appreciated.
IValueConverter:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Data;
namespace Test_Tool.Common
{
public class BooleanToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language) => (bool)value ^ (parameter as string ?? string.Empty).Equals("Reverse") ? Visibility.Visible : Visibility.Collapsed;
public object ConvertBack(object value, Type targetType, object parameter, string language) => (Visibility)value == Visibility.Visible ^ (parameter as string ?? string.Empty).Equals("Reverse");
}
}
XAML:
<Page
x:Class="Test_Tool.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Test_Tool"
xmlns:converter="using:Test_Tool.Common"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.Resources>
<converter:BooleanToVisibilityConverter x:Key="cvt" />
</Grid.Resources>
<Pivot x:Name="rootPivot" Title="Test Tool" >
<PivotItem Header="Test Selection">
<StackPanel>
<CheckBox x:Name="dppCheckBox" Content="DPP" Margin="5,8,5,5" Visibility="{Binding IsDirect, Converter={StaticResource cvt}}" />
</StackPanel>
</PivotItem>
</Pivot>
</Grid>
</Page>
ViewModel:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace Test_Tool.ViewModels
{
public class MainPageViewModel : INotifyPropertyChanged
{
//Localized private vars
private bool _isDirect;
//Public vars for bindings
public bool IsDirect
{
get
{
return _isDirect;
}
set
{
_isDirect = value;
OnPropertyChanged();
}
}
public MainPageViewModel()
{
//Any Initialization
IsDirect = false;
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged([CallerMemberName]string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
MainPage:
using DM_API_Test_Tool.ViewModels;
using Windows.UI.Xaml.Controls;
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409
namespace Test_Tool
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
public MainPageViewModel ViewModel { get; set; }
public MainPage()
{
this.InitializeComponent();
this.ViewModel = new MainPageViewModel();
}
}
}
Try this
namespace Test_Tool.ViewModels
{
public class MainPageViewModel : INotifyPropertyChanged
{
private bool _isDirect = false;
public bool IsDirect
{
get
{
return _isDirect;
}
set
{
set { SetField(ref _isDirect, value, "isDirect"); }
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetField<T>(ref T field, T value, string propertyName)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
}
}
this should work now.
However I prefer the BindableBase method more (save the content below in a new class and call it something like BindableBase.cs)
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace Mvvm
{
public abstract class BindableBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = null)
{
if (object.Equals(storage, value)) return false;
storage = value;
this.OnPropertyChanged(propertyName);
return true;
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var eventHandler = this.PropertyChanged;
if (eventHandler != null)
{
eventHandler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
and then your ViewModel would be reduced to
using Mvvm;
namespace Test_Tool
{
public class MainPageViewModel : BindableBase
{
private bool _isDirect = false;
public bool IsDirect
{
get { return _isDirect; }
set { SetProperty(ref _isDirect, value); }
}
}
}
and one last thing: In your MainPage.xaml you want to bind to rootPivot, so you call
rootPivot.DataContext = null;
rootPivot.DataContext = new MainPageViewModel();
or something like that.
You can also try using external convertor library.
I found this nuget package:click here
This library is very simple to use, first install the nuget package and insert:
<Window x:Class="WPFUI.Views.MainWindow"
...
xmlns:convertor="clr-namespace:suren37.convertor;assembly=suren37.convertor"
...>
as namespace at the top of the view and you can now bind a boolean value to the visibility as
<CheckBox Grid.Row="1" IsChecked="{Binding IsVisibile}" Grid.Column="1" Margin="8 0" />
<TextBlock Grid.Row="1" Text="This text becomes visible on checked"
Visibility="{Binding IsVisibile, Converter={convertor:BoolToVisibilityConverter}}"
Grid.Column="2" TextWrapping="WrapWithOverflow"/>
To check out the working sample visit the github page, here.
I have a UserControl and I am loading it dynamically in a ContentControl on a Button click. I have a TextBlock in the UserControl and I want to show some text dynamically (which is basically a status which my another method will return while processing a request) in the TextBlock after the UserControl is loaded. I tried setting up this in a Loaded event of the UserControl but the text is already there when the UserControl is fully loaded and showed.
Can someone please give an idea about how to achieve this. I checked this and this link but none seems to be working for me.
Thanks Deepak
If your loading is logical rather than physical you must handle it yourself.
Particularly you must have a way to tell the UserControl when the data are loaded.
Here is a full sample using MVVM (not perfect):
The UserControl:
<UserControl x:Class="WpfApplication1.LoadDataView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
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">
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="boolToVizConverter"></BooleanToVisibilityConverter>
<local:ReverseBooleanToVisibilityConverter x:Key="reverseBoolToVizConverter"></local:ReverseBooleanToVisibilityConverter>
</UserControl.Resources>
<Grid>
<TextBlock Visibility="{Binding Model.IsLoadingData,Converter={StaticResource boolToVizConverter}}">...</TextBlock>
<TextBlock Visibility="{Binding Model.IsLoadingData,Converter={StaticResource reverseBoolToVizConverter}}">Data loaded</TextBlock>
</Grid>
</UserControl>
So "..." is displayed while we are loading (in a real application you would use a better UI control to render this status like a ProgressRing).
And when the data are loaded we display "Data loaded".
Code behind:
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication1
{
public partial class LoadDataView : UserControl
{
public LoadDataViewModel Model
{
get { return (LoadDataViewModel)GetValue(ModelProperty); }
set { SetValue(ModelProperty, value); }
}
public static readonly DependencyProperty ModelProperty = DependencyProperty.Register("Model", typeof(LoadDataViewModel), typeof(LoadDataView));
public LoadDataView()
{
InitializeComponent();
this.DataContext = this;
}
}
}
The view model:
using System.ComponentModel;
namespace WpfApplication1
{
public class LoadDataViewModel : INotifyPropertyChanged
{
private bool isLoadingData = false;
public bool IsLoadingData
{
get { return isLoadingData; }
set
{
if (value != isLoadingData)
{
isLoadingData = value;
PropertyChanged(this, new PropertyChangedEventArgs("IsLoadingData"));
}
}
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
}
}
Here is the main view:
<StackPanel>
<ContentControl x:Name="content"></ContentControl>
<Button Click="Button_Click">Display data</Button>
</StackPanel>
When we click the Button we display the data UserControl and we trigger the loading:
private async void Button_Click(object sender, RoutedEventArgs e)
{
LoadDataViewModel model = new LoadDataViewModel { IsLoadingData = true };
content.Content = new LoadDataView { Model = model };
await Task.Delay(3000);
model.IsLoadingData = false;
}
The async/await and Task stuff is just there to simulate the loading of the data.
Finally here is the reverse bool to visibility converter:
using System;
using System.Windows;
using System.Windows.Data;
namespace WpfApplication1
{
public class ReverseBooleanToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return !(bool)value ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
I have created a simple Silverlight user control and defined a public property AllowMultiple.
public bool AllowMultiple { get; set; }
Now, I am setting this public property in XAML as follows:
<Controls1:PeopleChooser Name="SinglePeopleChooser" AllowMultiple="False" Width="Auto" d:LayoutOverrides="Height"/>
<Controls1:PeopleChooser Name="MultiplePeopleChooser" AllowMultiple="True" Width="Auto" d:LayoutOverrides="Height"/>
I want to know, which is the best event I can get the value of this public property. I am trying to get this value inside of the constructor and trying to hide/show some controls inside my user controls but its not working.
public PeopleChooser()
{
InitializeComponent();
if (AllowMultiple)
{
UsersListBox.Visibility = System.Windows.Visibility.Visible;
UserTextBox.Visibility = System.Windows.Visibility.Collapsed;
ResolveButton.Visibility = Visibility.Collapsed;
}
else
{
UsersListBox.Visibility = System.Windows.Visibility.Collapsed;
UserTextBox.Visibility = System.Windows.Visibility.Visible;
ResolveButton.Visibility = Visibility.Visible;
}
}
Probably because during constructor initialization the value of this public property has not been assigned by framework to the object.
I was able to solve it through loaded event. There is no need for dependency property. Please see the code below. I can access the properties value successfully in Loaded event.
public PeopleChooser()
{
this.Loaded += PeopleChooser_Loaded;
InitializeComponent();
}
void PeopleChooser_Loaded(object sender, RoutedEventArgs e)
{
if (AllowMultiple)
{
UsersListBox.Visibility = System.Windows.Visibility.Visible;
UserTextBox.Visibility = System.Windows.Visibility.Collapsed;
ResolveButton.Visibility = Visibility.Collapsed;
}
else
{
UsersListBox.Visibility = System.Windows.Visibility.Collapsed;
UserTextBox.Visibility = System.Windows.Visibility.Visible;
ResolveButton.Visibility = Visibility.Visible;
}
Convert the public property with a backfield,
private bool _allowMultiple;
public bool AllowMultiple
{
get { return _allowMultiple; }
set { _allowMultiple = value; }
}
Place a break point in the setter and check is it hits on Constructor, if not you can use the Loaded event to check the same and make use of that.
If you use a dependency property, you can bind other elements properties to the AllowMultiple property of the people chooser and use a visibility converter to show/hide them. Example:
public partial class PeopleChooser : UserControl
{
public PeopleChooser()
{
InitializeComponent();
}
public static readonly DependencyProperty AllowMultipleProperty = DependencyProperty.Register("AllowMultiple", typeof(bool), typeof(PeopleChooser), null);
public bool AllowMultiple
{
get { return (bool)GetValue(AllowMultipleProperty); }
set { SetValue(AllowMultipleProperty, value); }
}
}
<UserControl x:Class="TestSilverlightApplication.MainPage"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400"
xmlns:lcl="clr-namespace:TestSilverlightApplication">
<UserControl.Resources>
<lcl:VisibilityConverter x:Key="VisibilityConverter" />
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White">
<StackPanel Orientation="Vertical">
<Button Click="Button_Click" Content="Toggle allow multiple" />
<lcl:PeopleChooser x:Name="lclPeopleChooser" AllowMultiple="False"></lcl:PeopleChooser>
<TextBlock Text="Dependent content" Visibility="{Binding AllowMultiple, ElementName=lclPeopleChooser, Converter={StaticResource VisibilityConverter}}" />
</StackPanel>
</Grid>
</UserControl>
private void Button_Click(object sender, RoutedEventArgs e)
{
lclPeopleChooser.AllowMultiple = !lclPeopleChooser.AllowMultiple;
}
public class VisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
bool parsedValue = false;
bool.TryParse(value.ToString(), out parsedValue);
if (parsedValue)
{
return Visibility.Visible;
}
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
This way you can avoid page events and potentially bind the AllowMultiple property to a view model property and let the UI take care of itself.
I'm building a program in WPF which must feature multi-language support, with the ability to switch language at run-time. My question concerns the image part of the localization.
I've built a solution which does not work the way I had hoped it would work and I would like some help fixing these problems. The code posted below is only a demonstration of the concept I'm trying to achieve. My real program has quite many pictures, so I want to avoid putting them all in a list, updating them one-by-one.
My idea is to name the images according to what language they belong to. The OriginalSource property (in lack of a better name) is formatted like "Koala.(lang).jpg", and the two images for English and French are called "Koala.en-GB.jpg" and "Koala.fr-FR.jpg".
My problem is that without the code which is commented at (1), the images will not be assigned a "real" Source (in the Image class).
Also, after having used the code at (1) (which violates my wish not to use an enumeration of all images), the "real" source is not updated at (2) at the click on the Button. My hopes were that (3) and (4) would solve these problems but apparently they don't.
Help would be much appreciated.
Code follows:
MainWindow.xaml (incorrect)
<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="700" Width="525">
<Window.Resources>
<local:LanguageCodeSetter x:Key="CodeSetter" LanguageCodeValue="en-GB" />
</Window.Resources>
<StackPanel>
<local:LocalizedImage x:Name="imgKoala" LanguageCode="{Binding Source={StaticResource CodeSetter}, Path=LanguageCodeValue, Mode=OneWay}" OriginalSource="Koala.(lang).jpg" Height="300" Stretch="Uniform" />
<local:LocalizedImage x:Name="imgPenguins" LanguageCode="{Binding Source={StaticResource CodeSetter}, Path=LanguageCodeValue, Mode=OneWay}" OriginalSource="Penguins.(lang).jpg" Height="300" Stretch="Uniform" />
<Button Content="Don't click here!" Click="Button_Click" />
</StackPanel>
</Window>
MainWindow.xaml.cs (incorrect)
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Imaging;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
private string LanguageCodeResource
{
get
{
return ((LanguageCodeSetter)Resources["CodeSetter"]).LanguageCodeValue;
}
set
{
((LanguageCodeSetter)Resources["CodeSetter"]).LanguageCodeValue = value;
}
}
public MainWindow()
{
InitializeComponent();
//(1)
//imgKoala.OriginalSource = imgKoala.OriginalSource;
//imgPenguins.OriginalSource = imgPenguins.OriginalSource;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
LanguageCodeResource = "fr-FR";
//(2)
//imgKoala.LanguageCode = imgKoala.LanguageCode;
//imgPenguins.LanguageCode = imgPenguins.LanguageCode;
}
}
public class LocalizedImage : Image
{
public static readonly DependencyProperty LanguageCodeProperty = DependencyProperty.Register("LanguageCode", typeof(string), typeof(LocalizedImage));
public static readonly DependencyProperty OriginalSourceProperty = DependencyProperty.Register("OriginalSource", typeof(string), typeof(LocalizedImage));
public string LanguageCode
{
get
{
return (string)GetValue(LanguageCodeProperty);
}
set
{
SetValue(LanguageCodeProperty, value);
//(3)
SetValue(SourceProperty, new BitmapImage(new Uri(OriginalSource.Replace("(lang)", value), UriKind.RelativeOrAbsolute)));
}
}
public string OriginalSource
{
get
{
return (string)GetValue(OriginalSourceProperty);
}
set
{
SetValue(OriginalSourceProperty, value);
//(4)
SetValue(SourceProperty, new BitmapImage(new Uri(value.Replace("(lang)", LanguageCode), UriKind.RelativeOrAbsolute)));
}
}
}
public class LanguageCodeSetter : INotifyPropertyChanged
{
private string _languageCode;
public event PropertyChangedEventHandler PropertyChanged;
public string LanguageCodeValue
{
get
{
return _languageCode;
}
set
{
_languageCode = value;
NotifyPropertyChanged("LanguageCodeValue");
}
}
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
}
#NVM
Point taken about confusing names. I've updated my code.
The reason that I use an INotifyPropertyChanged object is that I want the changes in one variable, namely the resource called CodeSetter, to propagate to all instances of LocalizedImage. The reason for this is that I'm building a WPF application with quite a lot of images, and I do not want to be forced to add them all in a list in code-behind (thus forgetting to add some images, and making future refactoring of the application more tedious). At a click on the button, the value of "LanguageCode" does change in all instances of LocalizedImage, so the propagation part seems to work. However, setting the "real" source at (3) does not. I've also tried setting base.Source to the same value (new BitmapImage(...)) but with the same result.
The property (LanguageCodeResource) is only for brevity in the Button_Click event handler.
Maybe I'm aiming in the wrong direction to solve this problem? Additional feedback would be much appreciated.
#NVM
That did the trick. Thank you very much!
For anyone interested, I attach my correct code. The somewhat cumbersome DataContext datatype is because I need "two datacontexts" for my images and texts (which come from an XML file) in my real program.
MainWindow.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:WpfApplication2"
Title="MainWindow" Height="700" Width="525">
<Window.Resources>
<local:LocalizedImageSourceConverter x:Key="localizedImageSourceConverter" />
</Window.Resources>
<StackPanel x:Name="layoutRoot">
<Image x:Name="imgKoala" Source="{Binding Path=LanguageCode, Converter={StaticResource localizedImageSourceConverter}, ConverterParameter='Koala.(lang).jpg'}" Height="300" Stretch="Uniform" />
<Image x:Name="imgPenguins" Source="{Binding Path=LanguageCode, Converter={StaticResource localizedImageSourceConverter}, ConverterParameter='Penguins.(lang).jpg'}" Height="300" Stretch="Uniform" />
<Button Content="Don't click here!" Click="Button_Click" />
</StackPanel>
MainWindow.cs.xaml
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
private string LanguageCodeValue
{
set
{
layoutRoot.DataContext = new
{
LanguageCode = value
};
}
}
public MainWindow()
{
InitializeComponent();
LanguageCodeValue = "en-GB";
}
private void Button_Click(object sender, RoutedEventArgs e)
{
LanguageCodeValue = "fr-FR";
}
}
public class LocalizedImageSourceConverter : IValueConverter
{
public object Convert(object values, Type targetType, object parameter, CultureInfo culture)
{
return ((string)parameter).Replace("(lang)", (string)values);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
Firstly you ought to stop using 'LanguageCode' to name just about everything. It is really confusing :D
Secondly for
<Window.Resources>
<local:LanguageCodeSetter x:Key="LanguageCode" LanguageCode="en-GB" />
</Window.Resources>
to make any sense
public string LanguageCode
{
get
{
return _languageCode;
}
set
{
_languageCode = value;
NotifyPropertyChanged("LanguageCode");
}
}
ought to be a dependency property and not a clr property backed by INotify...
EDIT
I still dont get how the LanguageCode property will work in the resources section.
Anyway having understood what you are trying to achieve here, there is a very simple solution
<Image Source="{Binding Path=LanguageCode, Converter={StaticResource localizedImageSourceConverter}, ConverterParameter='Koala.jpg'}"/>
public class LocalizedImageSourceConverter : IValueConverter
{
public object Convert(object values, Type targetType, object parameter, CultureInfo culture)
{
string fileName = Path.GetFileNameWithoutExtension((string)parameter);
string extension = Path.GetExtension((string)parameter);
string languageCode = (string)values;
return string.Format("{0}{1}{2}", fileName, languageCode, extension);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Instead of binding the Source property to a filePath(URI), I am binding it to the LanguageCode property. The LanguageCode property should be in your ViewModel or whatever datacontext object you are binding to.
The converter will take the path to the base image as a parameter and combines it with the bound LanguageCodeProperty to give you a localized path. And since you are binding to the LanguageCode property in your datacontext whenever it changes all images will be automatically updated. Note that the converter parameter cannot be bound. If you want to bind both the filePath and the language code use a multibinding.
*There might be syntax errors in the code, I am only trying to convey the concept