Do something after the usercontrol is loaded and displayed WPF - c#

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();
}
}
}

Related

WPF - Force Binding on Invisible ComboBox

I have a WPF window that contains multiple user controls, some of which are invisible (Visibility = Hidden). One of these controls has a ComboBox that has an ItemsSource binding, and I want to preset its selected item while the window/control is loading.
However, it seems like the binding is not applied until the combobox is visible. When I go to set the SelectedItem property and I hit a breakpoint in the debugger, I notice that ItemsSource is null at that moment. Is there a way to force WPF to apply the data binding and populate the combobox while it stays invisible?
Reproducible Example:
MainWindow.xaml
<Window x:Class="HiddenComboBoxBinding.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:HiddenComboBoxBinding"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Border x:Name="comboboxParent" Visibility="Collapsed">
<ComboBox x:Name="cmbSearchType" SelectedIndex="0" ItemsSource="{Binding SearchTypeOptions}" DisplayMemberPath="Name" SelectionChanged="cmbSearchType_SelectionChanged" />
</Border>
</Grid>
</Window>
MainWindow.xaml.cs
using System.Linq;
using System.Windows;
using System.Windows.Controls;
namespace HiddenComboBoxBinding
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private ViewModel viewModel { get; set; } = new ViewModel();
public MainWindow()
{
InitializeComponent();
this.DataContext = viewModel;
// Add some or all of our search types - in the real code, there's some business logic here
foreach (var searchType in SearchType.AllSearchTypes)
{
viewModel.SearchTypeOptions.Add(searchType);
}
// Pre-select the last option, which should be "Bar"
cmbSearchType.SelectedItem = SearchType.AllSearchTypes.Last();
}
private void cmbSearchType_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
}
}
}
ViewModel.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HiddenComboBoxBinding
{
public class ViewModel : INotifyPropertyChanged
{
public ObservableCollection<SearchType> SearchTypeOptions { get; set; } = new ObservableCollection<SearchType>();
#region INotifyPropertyChanged Members
private void NotifyPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); }
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
public class SearchType
{
// Source list of Search Types
private static List<SearchType> _AllSearchTypes;
public static List<SearchType> AllSearchTypes
{
get
{
if(_AllSearchTypes == null)
{
_AllSearchTypes = new List<SearchType>();
_AllSearchTypes.Add(new SearchType() { Name = "Foo" });
_AllSearchTypes.Add(new SearchType() { Name = "Bar" });
}
return _AllSearchTypes;
}
}
// Instance properties - for the purposes of a minimal, complete, verifiable example, just one property
public string Name { get; set; }
}
}
I was able to figure out the issue. Setting the SelectedItem actually did work (even though ItemsSource was null at that time) but in the XAML, the ComboBox had SelectedIndex="0" and it was taking precedence over the SelectedItem being set in the code-behind.

Access public property of silverlight user control in code behind

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.

Why isn't this binding working in MVVM?

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.

IValueConverter.Convert doesn't get called on a OneWay bind

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"));

Localize images in WPF

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

Categories