I'm setting up a binding to a ViewModel object from a WPF View, but the binding doesn't seem to be firing. I haven't done much work in MVVM, so I thought I'd ask to see if there's a reason why the binding to the image's source property isn't firing when the page is loaded.
Here's the page's XAML:
<Page x:Class="DallasPrintManager.PrintPage"
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:viewmodel="clr-namespace:DallasPrintManager.ViewModel"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="900"
DataContext="{Binding Main, Source={StaticResource PrintPage}}">
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="200" />
</Grid.ColumnDefinitions>
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<Image Source="{Binding ImageDisplay}" />
</ScrollViewer>
</Grid>
</Page>
And here's the ViewModel:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Windows.Media.Imaging;
using System.Drawing;
using System.Windows.Input;
using DallasPrintManager.Commands;
using System.Net;
using System.IO;
using System.Windows;
public class PrintPageViewModel : INotifyPropertyChanged
{
private BitmapImage _imageDisplay;
public PrintPageViewModel()
{
ImageDisplay = getImage();
}
private BitmapImage getImage()
{
try
{
WebClient wc = new WebClient();
byte[] imageData = wc.DownloadData("http://localhost/TestImage.tif");
MemoryStream ms = new MemoryStream();
ms.Write(imageData, 0, imageData.Length);
System.Windows.Media.Imaging.BitmapImage wpfImg = new System.Windows.Media.Imaging.BitmapImage();
wpfImg.BeginInit();
wpfImg.StreamSource = ms;
wpfImg.EndInit();
return wpfImg;
//return (Bitmap)Bitmap.FromStream(ms);
}
catch (WebException e)
{
MessageBox.Show("Error fetching document:\n\n" + e.Message);
return null;
}
catch (Exception e)
{
if (e.Source == "System.Drawing")
MessageBox.Show("Error reading document.");
Console.Out.Write(e);
return null;
}
}
public BitmapImage ImageDisplay
{
get
{
return _imageDisplay;
}
set
{
if (value != _imageDisplay)
{
_imageDisplay = value;
OnPropertyChanged("ImageDisplay");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}
}
The viewmodel is instantiated in app.xaml, and bound to by the print page.
Look in your Debug Output window. Do you see any binding errors? They pretty much tell you what you need.
If the Debug Output Window is not shown you can enable it with CTRL + Alt + O.
For further information add xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase" inside your XAML and diag:PresentationTraceSources.TraceLevel=High to your binding:
<Page x:Class="DallasPrintManager.PrintPage"
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:viewmodel="clr-namespace:DallasPrintManager.ViewModel"
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
[...]
DataContext="{Binding Main, Source={StaticResource PrintPage}}">
and:
<Image Source="{Binding ImageDisplay, diag:PresentationTraceSources.TraceLevel=High}" />
You should also make sure your properties of the bindings implement INotifyPropertyChanged if the bindings make use of UpdateSourceTrigger=PropertyChanged. Otherwise they will not update.
You can even create a dummyConverter:
public class DebugDummyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Debugger.Break();
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Debugger.Break();
return value;
}
}
and add it inside your binding Converter={StaticResource DebugDummyConverter} to get information about your value of the binding, but this only works if the binding is correctly set otherwise you will not get any information.
See http://www.wpf-tutorial.com/data-binding/debugging/ for more details.
Related
A ComboBox object with a data binding, when I propram like below, the content doesn't display until a selection operation made.
XAML:
<Window x:Class="Recipe_GUI.ComboboxTest"
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:Recipe_GUI"
xmlns:src="clr-namespace:RecipeLib;assembly=RecipeLib"
mc:Ignorable="d"
Title="ComboboxTest" Height="450" Width="800">
<Window.Resources>
<src:DataType x:Key="DataType"/>
</Window.Resources>
<Grid>
<ComboBox HorizontalAlignment="Left" Margin="125,82,0,0" VerticalAlignment="Top" Width="120"
x:Name="C01"
ItemsSource="{StaticResource ResourceKey=DataType}"
SelectedValue="{Binding Path=DataType}"/>
<!--SelectedValue="{Binding Path=Text, ElementName=T01}"/>-->
<TextBox Text="{Binding ElementName=C01, Path=SelectedIndex}" Margin="125,141,309,249"/>
<TextBox Text="{Binding Path=DataType}" Margin="125,202,309,188" x:Name="T01"/>
</Grid>
</Window>
When I change the code to binding the ComboBox to TextBox "T01"("T01" binding to the same object), the ComboBox initial value displayed as expected. The code and presentation like below.
<ComboBox HorizontalAlignment="Left" Margin="125,82,0,0" VerticalAlignment="Top" Width="120"
x:Name="C01"
ItemsSource="{StaticResource ResourceKey=DataType}"
SelectedValue="{Binding Path=Text, ElementName=T01}"/>
<!--SelectedValue="{Binding Path=DataType}"/>-->
The other related code are like below.
XAML.CS:
using RecipeLib;
using System.Windows;
namespace Recipe_GUI
{
/// <summary>
/// Interaction logic for ComboboxTest.xaml
/// </summary>
public partial class ComboboxTest : Window
{
Param param = new Param();
public ComboboxTest()
{
InitializeComponent();
DataContext = param;
}
}
}
Class Param:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
namespace RecipeLib
{
public class Param : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private Enum_DataType dataType;
public Enum_DataType DataType
{
get { return dataType; }
set
{
dataType = value;
OnPropertyChanged("DataType");
}
}
public Param()
{
DataType = Enum_DataType.Float;
}
}
}
Enum_DataType and DataType:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
namespace RecipeLib
{
public enum Enum_DataType : int
{
Float = 0,
Integer = 1,
Enumeration = 2,
Bitmask = 3,
Time = 4
}
public class DataType : ObservableCollection<string>
{
public DataType() : base()
{
foreach (var item in Enum.GetNames(typeof(Enum_DataType)))
{
Add(item);
}
}
}
}
Please help answer why the first method doesn't work for the initial value, and how to fix? Thank you in advance!
Old Code
<ComboBox HorizontalAlignment="Left" Margin="125,82,0,0"
VerticalAlignment="Top" Width="120"
x:Name="C01"
ItemsSource="{StaticResource ResourceKey=DataType}"
SelectedValue="{Binding Path=DataType}"/>
it's an assumption so if anyone can correct me then please do it.
I believe the ComboBox has a "Text" property and in this case, you are binding an enum so you need a converter to convert the value from enum to string. so I added a converter and it worked.
Additional: each ComboBoxItem has a "Content" property which is a string type so when you choose any of the items the ComboBox "Text" property is set automatically. that's why it works when you select any of the items.
the same logic applies to your code where you bound the text property of Textblock to ComboBox property "SelectedValue"
Updated Code
Here are some modifications I did to your existing code
Converter class
Option: I named it "EnumToStringConverter" but you can name this class whatever you want
public class EnumToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return Enum.GetNames(value.GetType()).ElementAt((int)value);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return Enum.Parse(targetType, value.ToString());
}
}
XAML
<Window.Resources>
<local:DataType x:Key="DataType"/>
<local:EnumToStringConverter x:Key="EnumConverter" />
</Window.Resources>
<ComboBox HorizontalAlignment="Left" Margin="125,82,0,0" VerticalAlignment="Top" Width="120"
x:Name="C01"
ItemsSource="{StaticResource DataType}"
SelectedValue="{Binding DataType, Converter={StaticResource EnumConverter}}" />
i have five textbox controls in a user control, i want to add a dependency property in such a way
public string MyValue
{
get
{
return Textbox1.Text.Trim() + "." + Textbox2.Text.Trim() + "." + Textbox3.Text.Trim() + "." + Textbox4.Text.Trim() + "|" + Textbox5.Text.Trim();
}
set
{
Textbox1.Text = value.Split('|')[0];
Textbox2.Text = value.Split('|')[1];
Textbox3.Text = value.Split('|')[2];
Textbox4.Text = value.Split('|')[3];
Textbox5.Text = value.Split('|')[4];
}
}
But it is not working. how do i create a dependency property which can be binded directly to a single property. Any help would be grateful.
There is more than one solution:
expose the full value with the property and use an IValueConverter to extract the parts
create five properties, each exposing a part of the full value
Both are MVVM-compliant but the second one may be more transparent by avoiding too much plumbing but you may need more notification (INotifyPropertyChanged) calls.
EDIT: complete implementation
The UserControl:
XAML:
<UserControl x:Class="WpfApplication1.SplitterControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<local:SplitConverter x:Key="splitConverter"></local:SplitConverter>
</UserControl.Resources>
<StackPanel x:Name="root" DataContext="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=MyValue,Mode=TwoWay,Converter={StaticResource splitConverter}}">
<TextBox x:Name="Textbox1" Text="{Binding [0],NotifyOnSourceUpdated=True,UpdateSourceTrigger=PropertyChanged}" SourceUpdated="TextBox_SourceUpdated"></TextBox>
<TextBox x:Name="Textbox2" Text="{Binding [1],NotifyOnSourceUpdated=True,UpdateSourceTrigger=PropertyChanged}" SourceUpdated="TextBox_SourceUpdated"></TextBox>
<TextBox x:Name="Textbox3" Text="{Binding [2],NotifyOnSourceUpdated=True,UpdateSourceTrigger=PropertyChanged}" SourceUpdated="TextBox_SourceUpdated"></TextBox>
<TextBox x:Name="Textbox4" Text="{Binding [3],NotifyOnSourceUpdated=True,UpdateSourceTrigger=PropertyChanged}" SourceUpdated="TextBox_SourceUpdated"></TextBox>
<TextBox x:Name="Textbox5" Text="{Binding [4],NotifyOnSourceUpdated=True,UpdateSourceTrigger=PropertyChanged}" SourceUpdated="TextBox_SourceUpdated"></TextBox>
</StackPanel>
</UserControl>
Code behind:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace WpfApplication1
{
public partial class SplitterControl : UserControl
{
public string MyValue
{
get { return (string)GetValue(MyValueProperty); }
set { SetValue(MyValueProperty, value); }
}
public static readonly DependencyProperty MyValueProperty = DependencyProperty.Register("MyValue", typeof(string), typeof(SplitterControl));
public SplitterControl()
{
InitializeComponent();
}
private void TextBox_SourceUpdated(object sender, DataTransferEventArgs e)
{
root.GetBindingExpression(DataContextProperty).UpdateSource();
}
}
}
The IValueConverter:
using System;
using System.Globalization;
using System.Windows.Data;
namespace WpfApplication1
{
public class SplitConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (value as string).Split('|');
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return string.Join("|", value as string[]);
}
}
}
And in the parent control, e.g. the MainWindow:
<TextBox x:Name="input" Text="First|Second|Third|Fourth|Fifth"></TextBox>
<local:SplitterControl MyValue="{Binding ElementName=input,Path=Text,Mode=TwoWay}"></local:SplitterControl>
Edit the "input" TextBox to change the full string value and edit each TextBox in the UserControl to change each part.
Very tricky but should do what you want.
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 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"));
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