I'm trying to implement a language selection system for my modernui wpf application. When language is switched from the combobox this change would be propagated to all of the application controls by means of databinding.
I've built the skeleton as follows:
languages are objects collected in a List
strings and their counterparts are stored in a static dictionary
data for binding is fetched by a Translation object
views have binding to the datacontext which is based on language framework
Below is the stripped down version of the functionalty and link to the sample vs2013 project. I tried INotify.. without success, I could only manage to update the binding target by resetting datacontext of the view (page1.xaml). Unfortunately couldn't update target on the other view (home.xaml). Question comes down to: "How to update all targets in all views at the same time?"
I'll appreciate any help and suggestions to setup a proper binding structure.
download sample project: http://goo.gl/yjSsKm
home.xaml
<Grid Style="{StaticResource ContentRoot}">
<ScrollViewer>
<StackPanel MinWidth="200">
<TextBlock Text="{Binding home_text_1}"/>
</StackPanel>
</ScrollViewer>
</Grid>
page1.xaml
<Grid Style="{StaticResource ContentRoot}">
<ScrollViewer>
<StackPanel MinWidth="200">
<TextBlock Text="{Binding page1_text_1}"/>
<ComboBox x:Name="cbox_lang" Width="120" HorizontalAlignment="Left" Margin="0,50,0,0" SelectionChanged="cbox_lang_SelectionChanged"/>
</StackPanel>
</ScrollViewer>
</Grid>
MainWindow.xaml.cs
using DynamicDataBinding.Pages;
using FirstFloor.ModernUI.Windows.Controls;
namespace DynamicDataBinding
{
public partial class MainWindow : ModernWindow
{
public MainWindow()
{
InitializeComponent();
Language lang_1 = new Language("Language 1");
Language lang_2 = new Language("Language 2");
Global.availableLanguages.Add(lang_1);
Global.availableLanguages.Add(lang_2);
Global.currentLanguage = lang_1;
Global.currentLanguage.set();
}
}
}
page1.xaml.cs
using System.Collections.Generic;
using System.Windows.Controls;
namespace DynamicDataBinding.Pages
{
public partial class Page1 : UserControl
{
public Page1()
{
InitializeComponent();
foreach (Language lang in Global.availableLanguages)
{
cbox_lang.Items.Add(lang.Name);
}
cbox_lang.SelectedItem = Global.currentLanguage.Name;
this.DataContext = Global.currentLanguage.FrameWork;
}
private void cbox_lang_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (cbox_lang.SelectedItem.ToString() != Global.currentLanguage.Name)
{
string selectedLanguage = cbox_lang.SelectedItem.ToString();
Global.currentLanguage = Global.availableLanguages.Find(lang => lang.Name == selectedLanguage);
Global.currentLanguage.set();
DataContext = null;
DataContext = Global.currentLanguage.FrameWork;
}
}
}
public class Global
{
public static Dictionary<string, string> dictionary = new Dictionary<string, string>();
public static List<Language> availableLanguages = new List<Language>();
public static Language currentLanguage;
}
public class Language
{
public string Name;
public Translation FrameWork;
public Language(string name)
{
this.Name = name;
}
public void set()
{
Global.dictionary.Clear();
if (Global.currentLanguage.Name == "Language 1")
{
Global.dictionary.Add("home_content_1", "Content For Home in Language 1");
Global.dictionary.Add("page1_content_1", "Content For Page1 in Language 1");
}
else if (Global.currentLanguage.Name == "Language 2")
{
Global.dictionary.Add("home_content_1", "Different Content For Home in Language 2");
Global.dictionary.Add("page1_content_1", "Different Content For Page1 in Language 2");
}
FrameWork = new Translation();
}
}
public class Translation
{
public string home_text_1 { get { return Global.dictionary["home_content_1"]; } }
public string page1_text_1 { get { return Global.dictionary["page1_content_1"]; } }
}
}
home.xaml.cs
using System.Windows.Controls;
namespace DynamicDataBinding.Pages
{
public partial class Home : UserControl
{
public Home()
{
InitializeComponent();
this.DataContext = Global.currentLanguage.FrameWork;
}
}
}
At the end of the day (eventually) I figured out that there is no need to deal with datacontext. Implementing INotifyPropertyChanged handles all of the target updates if it is setup properly. Furthermore use of a dictionary is not suitable to hold data if binding is in question. Below is the corrected version of the code:
page1.xaml.cs
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Controls;
namespace DynamicDataBinding.Pages
{
/// <summary>
/// Interaction logic for Page1.xaml
/// </summary>
public partial class Page1 : UserControl
{
public Page1()
{
InitializeComponent();
foreach (Language lang in Global.availableLanguages)
{
cbox_lang.Items.Add(lang.Name);
}
cbox_lang.SelectedItem = Global.currentLanguage.Name;
this.DataContext = Global.FrameWork;
}
private void cbox_lang_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (cbox_lang.SelectedItem.ToString() != Global.currentLanguage.Name)
{
string selectedLanguage = cbox_lang.SelectedItem.ToString();
Global.currentLanguage = Global.availableLanguages.Find(lang => lang.Name == selectedLanguage);
Global.currentLanguage.set();
DataContext = null;
DataContext = Global.FrameWork;
}
}
}
public class Global
{
public static Dictionary<string, string> dictionary = new Dictionary<string, string>();
public static List<Language> availableLanguages = new List<Language>();
public static Language currentLanguage;
public static Translation FrameWork = new Translation();
}
public class Language
{
public string Name;
public Language(string name)
{
this.Name = name;
}
public void set()
{
Global.dictionary.Clear();
if (Global.currentLanguage.Name == "Language 1")
{
Global.FrameWork.home_text_1 = "Content For Home in Language 1";
Global.FrameWork.page1_text_1 = "Content For Home in Language 1";
}
else if (Global.currentLanguage.Name == "Language 2")
{
Global.FrameWork.home_text_1 = "Different Content For Home in Language 2";
Global.FrameWork.page1_text_1 = "Different Content For Home in Language 2";
}
}
}
public class Translation : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private string _home_text_1;
private string _page1_text_1;
public string home_text_1 { get {return _home_text_1;} set { _home_text_1 = value; OnPropertyChanged("home_text_1"); }}
public string page1_text_1 { get {return _page1_text_1;} set { _page1_text_1 = value; OnPropertyChanged("page1_text_1");}}
}
}
Related
I have TextBlock binded manually in MainWindow.xaml
<TextBlock Name="TestPrice"
Height="30"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Text="{Binding Path=
ScreenMarketLogger, Mode=Default, UpdateSourceTrigger=PropertyChanged}"/>
In MainWindow.xaml.cs I define class with properties:
public class ScreenLoggerBind : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _ScreenMarketLogger;
public string ScreenMarketLogger
{
get
{
return _ScreenMarketLogger;
}
set
{
_ScreenMarketLogger = value;
OnPropertyChanged("ScreenMarketLogger");
}
}
private string _CurrentPrice;
public string CurrentPrice
{
get
{
return _CurrentPrice;
}
set
{
_CurrentPrice = value;
OnPropertyChanged("CurrentPrice");
}
}
private void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
public ScreenLoggerBind()
{
this.ScreenMarketLogger = "\r\n begin \r\n";
}
}
I have another class (physically this is separate file) where I define constructor for ScreenLoggerBind class.
class ExternalClass
{
...
ScreenLoggerBind ScreenLogger = new ScreenLoggerBind();
...
}
Now I transfer DataContext into this class like this:
public void Init(MainWindow mw)
{
mw.TestPrice.DataContext = ScreenLogger;
}
And call this function in MainWindow.xaml.cs in the mainWindow method like this
ExternalClass ext = new ExternalClass()
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
ext.Init(this);
}
And if I assign a value to a variable ScreenLogger.ScreenMarketLogger I see result on main WPF form.
All works properly here.
Now question. If I create component dynamically in MainWindow.xaml.cs, like this for example:
Label lbl_Price = new Label();
lbl_Price.Name = string.Format("lbl_Price_{0}{1}", i.ToString(), cell.ToString());
Binding lbl_PriceBinding = new Binding("Content");
lbl_PriceBinding.Source = ScreenLogger.CurrentPrice;
lbl_PriceBinding.Mode = BindingMode.Default;
lbl_PriceBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
lbl_Price.SetBinding(Label.ContentProperty, lbl_PriceBinding);
....
And define DataContext in external class ExternalClass.cs
public void Init(MainWindow mw)
{
mw.TestPrice.DataContext = ScreenLogger;
foreach (Label lbl in mw.ChainGrid.Children.OfType<System.Windows.Controls.Label>())
{
if (lbl.Name == "lbl_XName_Price_00")
{
lbl.DataContext = ScreenLogger;
}
}
}
This is doesn't work! I see created dynamically Label on main form. But if I assign value to ScreenLogger.CurrentPrice variable I don't see any changes.
Why? where I made mistake?
Try to do as below:
Binding lbl_PriceBinding = new Binding("CurrentPrice");
lbl_PriceBinding.Source = ScreenLogger;
lbl_PriceBinding.Mode = BindingMode.OneWay;
You must provide the path to property of a source in Binding constructor. In your case the source is ScreenLogger and path, relative to it, is CurrentPrice.
I have a public boolean in my UWP app used to show/hide a ProgressBar. I've used the same pattern elsewhere and it seems to work okay, but I'm having an issue on a specific page where it doesn't seem to update in the UI only if I set the boolean after an awaited async call (same pattern as the working page).
Here is my XAML View:
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<TextBlock Text="{ x:Bind Vm.IsLoaded }" Margin="112,272,-112,-272"></TextBlock>
</Grid>
And the codebehind:
public sealed partial class MainPage : Page
{
public MainPageViewModel Vm => DataContext as MainPageViewModel;
public MainPage()
{
this.InitializeComponent();
this.DataContext = new MainPageViewModel();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
Vm.GetProjectData();
}
}
Here is my MainPageViewModel.cs
public class MainPageViewModel : ViewModel
{
public ObservableCollection<Project> Projects { get; set; } = new ObservableCollection<Project>();
private bool _isLoaded;
public bool IsLoaded
{
get { return _isLoaded; }
set
{
_isLoaded = value;
OnPropertyChanged();
}
}
public async Task GetProjectData()
{
// If I put `IsLoaded = true;` here it displays `true` in the UI
var projectsResponse = await HttpUtil.GetAsync(StringUtil.ProjectsUrl());
// If I put `IsLoaded = true;` here it still displays `false` in the UI
if (projectsResponse.IsSuccessStatusCode)
{
var projectsResponseString = await projectsResponse.Content.ReadAsStringAsync();
var projects = JsonUtil.SerializeJsonToObject<List<Project>>(projectsResponseString);
foreach (var project in projects)
{
Projects.Add(project);
}
IsLoaded = true;
}
}
}
And my ViewModel.cs
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
No matter where I put the IsLoaded = true; it always hits OnPropertyChanged().
Here is my working code:
ProjectViewViewModel.cs:
public class ProjectViewViewModel : ViewModel
{
public ObservableCollection<Story> MyData { get; set; } = new ObservableCollection<Story>();
private bool _dataIsLoaded;
public bool DataIsLoaded
{
get { return _dataIsLoaded; }
set
{
_dataIsLoaded = value;
OnPropertyChanged();
}
}
public async Task GetData(Project project)
{
DataIsLoaded = false;
var stringResponse = await HttpUtil.GetAsync(StringUtil.Query(project.Id, "MB"));
if (stringResponse.IsSuccessStatusCode)
{
// Do Stuff
DataIsLoaded = true;
}
}
}
ProjectView.xaml.cs
public sealed partial class ProjectView : Page
{
public Project Project { get; set; }
public bool IsLoaded { get; set; }
public ProjectViewViewModel Vm => DataContext as ProjectViewViewModel;
public ProjectView()
{
this.InitializeComponent();
this.DataContext = new ProjectViewViewModel();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
Project = e.Parameter as Project;
Vm.GetData(Project);
}
}
I feel like I'm missing something extremely obvious but I can't see the wood through the trees and it's driving me nuts. Any help is greatly appreciated!
I believe the problem you are having is in your mark up. x:Bind has a default binding mode of OneTime; so the text in your text block is bound to the value of IsLoaded at application start up, or when the data context for the text block changed.
Setting the binding mode to OneWay should result in the value in the text block updating after the async function has returned.
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<TextBlock Text="{ x:Bind Path=Vm.IsLoaded, Mode=OneWay }" Margin="112,272,-112,-272"></TextBlock>
</Grid>
If you're interested, this article goes into detail on the use of x:Bind. Also, this article covers the values in the BindingMode enumeration.
Alright, so I have luck of running into a lot of basic problems. I can't figure a way around this particular issue.
This piece of code needs to access "_Player.Name" property of object created in "MainWindow" class.
Edit: Putting up the whole code this time. Here's the Code_Behind where the string is.
public class Code_Behind
{
private static string _Name = "Default";
public class Player
{
public void setName(string name) //Ignore this part, was trying to find a work around here
{
_Name = name;
}
public string Name
{
get { return _Name; }
set
{
_Name = value;
}
}
}
//contentControl is used to store Content properties
//UI elements are bound to Content properties to efficiently change their Content
public class contentControl : INotifyPropertyChanged
{
protected void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
public void setEvent(string Event)
{
textBoxContent = Event;
}
public void addEvent(string Event)
{
textBoxContent +="\n" + Event;
}
public class Events
{
public string EV001 = String.Format("\"Greetings {0}. What can I do for you today?\"", window.PlayerName);
}
}
And here is the MainWindow one:
public partial class MainWindow : Window
{
Code_Behind.contentControl cC = new Code_Behind.contentControl();
Code_Behind.contentControl.Events Events = new Code_Behind.contentControl.Events();
Code_Behind.Player _Player = new Code_Behind.Player();
public string GetPlayerName()
{
return _Player.Name;
}
public static string _name = "null";
public MainWindow()
{
this.DataContext = cC;
InitializeComponent();
}
public string GetPlayerName()
{
return _Player.Name
}
Create a method in your MainWindow class. After that you call this method.
public string EV001 = String.Format("\"Greetings {0}. What can I do for you today?\"",
window.GetPlayerName());
You can do it with property too if you want.
public string PlayerName
{
get { return _Player.Name; };
}
The bigger problem you have here is not about accessibility, but not understanding the difference between a class and an object.
MainWindow is a class. It does not represent any specific window. Think of a class like a recipe to create objects. If you had a chocolate chip cookie recipe, you don't eat the recipe, you eat a specific cookie or cookies baked following that recipe.
Your other class first needs to know which specific window you are trying to get the player name from. It needs a reference to a particular MainWindow object.
It looks like you're trying write something like a viewmodel: You've got a player, he has a name, and there's a collection of strings that you think of as "events". I don't understand what the "events" are meant to be, but I implemented my best guess at what I think you seem to be trying to do.
As for this:
public class Events
{
public string EV001 = String.Format("\"Greetings {0}. What can I do for you today?\"", window.PlayerName);
}
I guess you created an instance of MainWindow somewhere, and called it window, but it's defined someplace where it's "out of scope" for that line of code. By analogy, you can't see anything that's behind the next hill, only stuff that's in the valley you're standing in. That's roughly (very roughly, sorry) kind of what scope is about.
But let's move on to my guess at what you're trying to do. This builds, runs, and works. Any questions at all, fire away.
ViewModels.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 Player
{
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
public class MainViewModel : ViewModelBase
{
#region Player Property
private PlayerViewModel _player = default(PlayerViewModel);
public PlayerViewModel Player
{
get { return _player; }
set
{
if (value != _player)
{
_player = value;
OnPropertyChanged(nameof(Player));
// Change the player for all the existing events.
foreach (var e in Events)
{
e.Player = Player;
}
}
}
}
#endregion Player Property
private ObservableCollection<Event> _events = new ObservableCollection<Event>();
public ObservableCollection<Event> Events
{
get { return _events; }
private set
{
if (value != _events)
{
_events = value;
OnPropertyChanged(nameof(Events));
}
}
}
#region Event Methods
// This is a BIG guess as to what you're trying to do.
public void AddGreeting()
{
// Player is "in scope" because Player is a property of this class.
if (Player == null)
{
throw new Exception("Player is null. You can't greet a player who's not there.");
}
Events.Add(new Event("\"Greetings {0}. What can I do for you today?\"", Player));
}
#endregion Event Methods
}
public class Employee : ViewModelBase
{
#region DisplayLtdOccupationId Property
private bool _displayLtdOccupationId = default(bool);
public bool DisplayLtdOccupationId
{
get { return _displayLtdOccupationId; }
set
{
if (value != _displayLtdOccupationId)
{
_displayLtdOccupationId = value;
OnPropertyChanged(nameof(DisplayLtdOccupationId));
}
}
}
#endregion DisplayLtdOccupationId Property
}
public class Event : ViewModelBase
{
public Event(String format, PlayerViewModel player)
{
_format = format;
Player = player;
}
private String _format = "";
public String Message
{
get { return String.Format(_format, Player.Name); }
}
#region Player Property
private PlayerViewModel _player = default(PlayerViewModel);
public PlayerViewModel Player
{
get { return _player; }
set
{
if (value != _player)
{
_player = value;
OnPropertyChanged(nameof(Player));
// When player changes, his name changes, so that
// means the value of Message will change.
OnPropertyChanged(nameof(Message));
if (_player != null)
{
_player.PropertyChanged += _player_PropertyChanged;
}
}
}
}
private void _player_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case nameof(PlayerViewModel.Name):
OnPropertyChanged(nameof(Message));
break;
}
}
#endregion Player Property
}
public class PlayerViewModel : ViewModelBase
{
private String _name = default(String);
public String Name
{
get { return _name; }
set
{
if (value != _name)
{
_name = value;
OnPropertyChanged(nameof(Name));
}
}
}
}
}
MainWindow.xaml.cs
using System.Windows;
namespace Player
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ViewModel = new MainViewModel();
ViewModel.Player = new PlayerViewModel() { Name = "Ivan the Terrible" };
}
// Just here as a convenience, and to make sure we don't give the DataContext
// the wrong kind of viewmodel.
public MainViewModel ViewModel
{
set { DataContext = value; }
get { return DataContext as MainViewModel; }
}
private void Greeting_Click(object sender, RoutedEventArgs e)
{
ViewModel.AddGreeting();
}
}
}
MainWindow.xaml
<Window
x:Class="Player.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:Player"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<StackPanel Orientation="Vertical">
<WrapPanel>
<Button x:Name="Greeting" Content="Greeting" Click="Greeting_Click" />
<Label>Name: </Label>
<TextBox Text="{Binding Player.Name}" Width="120" />
</WrapPanel>
<ListBox
ItemsSource="{Binding Events}"
DisplayMemberPath="Message"
>
</ListBox>
</StackPanel>
</Grid>
</Window>
You can change the set of Name to be private, but still allow the outside world to read the property with the get.
public string Name { get; private set; } = "Default";
This should give you the functionallity desired without the need to create a new GetName() method.
I'm trying to implement a real-time plot UI, i'm using WPF with the MVVM Pattern and Live-Charts by beto-rodriguez as my plot library, but i have some trouble with updating the graphs in real time. I know that i have to run multiple threads to update the UI in realtime, but every single way i tried doesn't work (i'm learning C# now).
I'm confused of how i should properly implement this pattern for the realtime update, and if the plot library is able to do that.
This is my actual code (its a simplified version of what i will do and doesn't implement any multithread code)
ModelView Code:
using System;
using System.ComponentModel;
using System.Windows;
using LiveCharts;
namespace TestMotionDetection
{
class MainViewModel : INotifyPropertyChanged
{
public SeriesCollection Series { get; set; }
public Func<double, string> YFormatter { get; set; }
public Func<double, string> XFormatter { get; set; }
public DataViewModel SData
{
set
{
Series[0].Values.Add(value);
OnPropertyChanged("Series");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public MainViewModel()
{
SeriesConfiguration<DataViewModel> config = new SeriesConfiguration<DataViewModel>();
config.Y(model => model.Value);
config.X(model => model.Time);
Series = new SeriesCollection(config)
{
new LineSeries {Values = new ChartValues<DataViewModel>(), PointRadius = 0}
};
XFormatter = val => Math.Round(val) + " ms";
YFormatter = val => Math.Round(val) + " °";
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public void generateData()
{
DataViewModel val = new DataViewModel();
for (int i = 0; i < 500; i++)
{
val.Time = i;
val.Value = i + 2.3f;
SData = val;
}
}
}
}
Here is the View code:
using System.Windows;
namespace TestMotionDetection
{
/// <summary>
/// Logica di interazione per MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private MainViewModel vista;
public MainWindow()
{
vista = new MainViewModel();
DataContext = vista;
}
private void button_Click(object sender, RoutedEventArgs e)
{
vista.generateData();
}
}
}
And the XALM:
<Window x:Class="TestMotionDetection.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:lvc="clr-namespace:LiveCharts;assembly=LiveCharts"
Title="Example 2 (WPF)"
Width="1025.213"
Height="482.801">
<Grid>
<lvc:LineChart Margin="0,2,245,-2"
LegendLocation="Right"
Series="{Binding Series}">
<lvc:LineChart.AxisX>
<lvc:Axis LabelFormatter="{Binding XFormatter}" Separator="{x:Static lvc:DefaultAxes.CleanSeparator}" />
</lvc:LineChart.AxisX>
<lvc:LineChart.AxisY>
<lvc:Axis LabelFormatter="{Binding YFormatter}" />
</lvc:LineChart.AxisY>
</lvc:LineChart>
<Button x:Name="button"
Width="151"
Height="79"
Margin="804,47,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Click="button_Click"
Content="Button" />
</Grid>
</Window>
[UPDATE 1]
[]1
Your XFormatter & YFormatter should both be like this:
private Func<double, string> _yFormatter;
public Func<double, string> YFormatter {
get{ return _yFormatter; }
set
{
_yFormatter = value;
OnPropertyChanged("YFormatter");
}
If you are using C#6 you should do nameof(YFormatter) instead.
That will cause the view to update, otherwise the view has no way of knowing that the formatter changed.
Is there a MVVM way to select text in a textbox? The MVVM framework that I am using is Laurent Bugnion's MVVM Light Toolkit.
Whenever I am trying to directly affect the the View in a "pure" MVVM application (no code-behind in View), I will use Attached Properties to encapsulate whatever effect I am trying to achieve. I will create an interface that defines the actions I wish to take using custom events. I then implement this interface in each ViewModel that will be "running" these commands on the View. Finally, I bind my ViewModel to the attached property in my View definition. The following code shows how to this for SelectAll and a TextBox. This code can be easily expanded to perform just about any action on any component in the View.
My Attached Property and interface definition:
using System.Windows;
using System.Windows.Controls;
using System;
using System.Collections.Generic;
namespace SelectAllSample
{
public static class TextBoxAttach
{
public static readonly DependencyProperty TextBoxControllerProperty = DependencyProperty.RegisterAttached(
"TextBoxController", typeof(ITextBoxController), typeof(TextBoxAttach),
new FrameworkPropertyMetadata(null, OnTextBoxControllerChanged));
public static void SetTextBoxController(UIElement element, ITextBoxController value)
{
element.SetValue(TextBoxControllerProperty, value);
}
public static ITextBoxController GetTextBoxController(UIElement element)
{
return (ITextBoxController)element.GetValue(TextBoxControllerProperty);
}
private static readonly Dictionary<ITextBoxController, TextBox> elements = new Dictionary<ITextBoxController, TextBox>();
private static void OnTextBoxControllerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var element = d as TextBox;
if (element == null)
throw new ArgumentNullException("d");
var oldController = e.OldValue as ITextBoxController;
if (oldController != null)
{
elements.Remove(oldController);
oldController.SelectAll -= SelectAll;
}
var newController = e.NewValue as ITextBoxController;
if (newController != null)
{
elements.Add(newController, element);
newController.SelectAll += SelectAll;
}
}
private static void SelectAll(ITextBoxController sender)
{
TextBox element;
if (!elements.TryGetValue(sender, out element))
throw new ArgumentException("sender");
element.Focus();
element.SelectAll();
}
}
public interface ITextBoxController
{
event SelectAllEventHandler SelectAll;
}
public delegate void SelectAllEventHandler(ITextBoxController sender);
}
My ViewModel definition:
public class MyViewModel : ITextBoxController
{
public MyViewModel()
{
Value = "My Text";
SelectAllCommand = new RelayCommand(p =>
{
if (SelectAll != null)
SelectAll(this);
});
}
public string Value { get; set; }
public RelayCommand SelectAllCommand { get; private set; }
public event SelectAllEventHandler SelectAll;
}
My View definition:
<Window x:Class="SelectAllSample.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:loc="clr-namespace:SelectAllSample"
Title="Window1" Height="150" Width="150">
<x:Code><![CDATA[
public Window1()
{
InitializeComponent();
DataContext = new MyViewModel();
}
]]></x:Code>
<StackPanel>
<TextBox Text="{Binding Value}" loc:TextBoxAttach.TextBoxController="{Binding}" />
<Button Content="Select All" Command="{Binding SelectAllCommand}" />
</StackPanel>
</Window>
Note: Thanks to Josh Smith for RelayCommand (see code in Figure 3 on this page). It is used in MyViewModel in this example (and just about all my MVVM code).
find a good introduction to attached properties here:
http://www.codeproject.com/KB/WPF/AttachedBehaviors.aspx