How do i use an Object of IValueConverter from DataContext? - c#

The DataContext of my WPF application contains an object of an IVariableConverter, which interacts with the DataContext, and is not static
// Simplified Example
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ViewModel viewModel = new ViewModel();
DataContext = viewModel;
}
}
//I am using Fody PropertyChanged
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = (sender, e) => { };
public int someIndex { get; set;}
// The converter that I want to use, which implements the IValueConverter interface
public SomeConverter someConverter { get; set; }
public ViewModel()
{
// ... setting the other variables ...
someConverter = new SomeConverter(this, ...);
}
}
Now I have a TextBlock, which uses a binding to the 'someIndex' variable, which I want to convert using the 'someConverter' from the DataContext (which is an object of 'ViewModel')
My best guess would have been to just use the converters name, like a binding, but that unfortunately didn't help
<TextBlock Text="{binding someIndex, Converter=someConverter}"/>
How do i access the converter?
Thanks for the help!

Related

Binding to an object property in ViewModel, or how to use multiple models with the same property names

I am trying to bind to a property of a Model via the ViewModel in an textbox. I created a wrapper in the ViewModel to access the property of the Model. Whenever I put a break point in the get section of this wrapper it triggers, but a break point in the set section is not executed after changing the textbox content. Code is according to the following tutorial: https://www.codeproject.com/Articles/1193164/MVVM-Sample-application-with-a-focus-in-property
The BaseViewModel implements the INPC interface. I am trying to follow the MVVM "rule of thumbs" as close as possible. So no INPC implementation in the model in this case, and the view can only bind to the VM.
What am I doing wrong?
XAML:
<TextBox Grid.Column="1" VerticalAlignment="Center" Text="{Binding Path=ProgramInfo.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
Model:
public class ProgramInfo
{
private string _name;
private string _description;
public string Name
{
get => _name;
set
{
_name = value;
}
}
public string Description
{
get => _description;
set
{
_description = value;
}
}
}
ViewModel:
class ProgramInfoViewModel : BaseViewModel
{
private ProgramInfo _programInfo;
public ProgramInfo ProgramInfo
{
get => _programInfo;
set
{
_programInfo = value;
OnPropertyChanged("ProgramInfo");
}
}
}
Code-behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ProgramInfoViewModel();
}
}
_programInfo is always null. you need to create an instance:
class ProgramInfoViewModel : BaseViewModel
{
private ProgramInfo _programInfo = new ProgramInfo();
public ProgramInfo ProgramInfo
{
get => _programInfo;
set
{
_programInfo = value;
OnPropertyChanged("ProgramInfo");
}
}
}
or
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ProgramInfoViewModel { ProgramInfo = new ProgramInfo() };
}
}
a view model wrapper for model property will look like this:
class ProgramInfoViewModel : BaseViewModel
{
private ProgramInfo _programInfo;
private ProgramInfoViewModel(ProgramInfo programInfo)
{
_programInfo = programInfo;
}
public string ProgramInfoName
{
get => _programInfo.Name;
set
{
_programInfo.Name = value;
OnPropertyChanged("ProgramInfoName");
}
}
}
initialization:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var model = new ProgramInfo();
DataContext = new ProgramInfoViewModel(model);
}
}
binding path has to change accordingly:
<TextBox Text="{Binding Path=ProgramInfoName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />

Why notification for List<T> property doesn't work

Why rising INotifypPropertyChanged for List<T> property doesn't work?
Consider this MCVE:
public class NotifyPropertyChanged : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string property = "") =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
public class TextWrapper
{
public string Text { get; set; }
public override string ToString() => Text;
}
public class ViewModel : NotifyPropertyChanged
{
public List<string> List { get; } = new List<string>();
public TextWrapper Text { get; } = new TextWrapper();
public void AddToList(string text)
{
List.Add(text);
OnPropertyChanged(nameof(List));
}
public void ChangeText(string text)
{
Text.Text = text;
OnPropertyChanged(nameof(Text));
}
}
public partial class MainWindow : Window
{
readonly ViewModel _vm = new ViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = _vm;
}
}
xaml:
<TextBlock Text="{Binding Text}" />
<ListBox ItemsSource="{Binding List}" />
Calling _vm.ChangeText(...) will properly update TextBlock, while calling _vm.AddToList(...) doesn't update ListBox (it will stay empty). Why?
Please note: I know about ObservableCollection<T> and I know about two possible workarounds (add setter to List and set it to e.g. null first and then back or change DataContext/ItemsSource). I am just curious what is under roof makes List<T> more special than TextWrapper.
When a WPF Binding handles the PropertyChanged event, it does not update its target property unless the effective value it produces has actually changed.
So unless the List property value actually changes (which it doesn't when you add an element), calling
OnPropertyChanged(nameof(List));
has no effect.
Replace
public List<string> List { get; } = new List<string>();
by
public ObservableCollection<string> List { get; } = new ObservableCollection<string>();
and write the AddToList method like this:
public void AddToList(string text)
{
List.Add(text);
}
For your TextWrapper class: Since you directly bind to the TextWrapper instance, the Binding calls its overridden ToString() method and hence produces a different value whenever the TextWrapper's Text property has changed.

Issue with databinding to class?

I'm having an issue with databinding a textblock to a custom prop inside another class, what am I doing wrong?
mainpage:
public partial class MainPage : PhoneApplicationPage
{
// Constructor
public MainPage()
{
InitializeComponent();
InitializeLanguage();
}
private void InitializeLanguage()
{
LanguageHelper lh = new LanguageHelper();
// this.TitlePanel.DataContext = lh;
txtTitle.DataContext = lh;
}
}
databinding:
<TextBlock x:Name="txtTitle"
Text="{Binding homepage_subheading}"
Style="{StaticResource PhoneTextNormalStyle}"
Foreground="White"
Margin="12,0"/>
LanguageHelper class:
public class LanguageHelper
{
public String homepage_subheading;
public void changeLanguage()
{
if (true)
{
//english
homepage_subheading = "This is the top / sub Heading";
}
}
}
You don't have a property but a public field, and the databinding engine only works on properties.
So you need to change your class:
public String homepage_subheading { get; set; }
If you want to also notify the UI with your changes of your properties your LanguageHelper should implement the INotifyPropertyChanged interface and fire the PropertyChange event when you modify your properties.
You should declare some dependency property or use INotifyPropertyChanged, I would like to use a dependency property:
public class LanguageHelper : DependencyObject {
public static DependencyProperty Hompage_subheadingProperty =
DependencyProperty.Register("Homepage_subheading", typeof(string), typeof(LanguageHelper));
public String Homepage_subheading {
get { return (string) GetValue(Homepage_subheadingProperty);}
set { SetValue(Homepage_subheadingProperty, value);}
}
}
Note about the naming convention in C#, all properties should have first letter capitalized.

change the MVVM WPF listview items on window initialization

I have a problem with listView initializations. The .xaml part of the listView is as below,
<ListView x:Name="categoryListView" HorizontalAlignment="Left" Width="129" Height="180"
ItemsSource="{Binding Path=RecordModel.CategoryList}"
DisplayMemberPath="RecordModel.CategoryList"
SelectedValue="{Binding Path=RecordModel.RecordTitle}"
VerticalAlignment="Top">
I have a list of String paths in RecordModel.CategoryList but I need to change the list at window initialization. Part of the view-model is below. Where can I add the code to change the list so the listView gets the changed list items at start?
public class MainWindowViewModel : ViewModelBase
{
...
private RecordModel _recordModel;
private ICommand _addCategoryCommand;
...
public MainWindowViewModel()
{
_recordModel = new RecordModel();
}
public RecordModel RecordModel
{
get { return _recordModel; }
set { _recordModel = value; }
}
...
public ICommand AddCategoryCommand
{
get
{
if (_addCategoryCommand == null)
_addCategoryCommand = new AddCat ();
return _addCategoryCommand;
}
}
public class AddCat : ICommand
{
public bool CanExecute(object parameter) { return true; }
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
MainWindowViewModel mainWindowViewModel = (MainWindowViewModel)parameter;
...
//Do things with mainWindowViewModel and the variables it has
}
...
This is the reason that ViewModels exist: so that they can transparently convert values from the Model to values more appropriate for binding.
You should expose a CategoryList property on the MainWindowViewModel and bind directly on that. You can then populate it by processing the values of RecordModel.CategoryList in the RecordModel property setter:
public class MainWindowViewModel : ViewModelBase
{
private RecordModel _recordModel;
public MainWindowViewModel()
{
RecordModel = new RecordModel(); // set the property not the field
}
public RecordModel RecordModel
{
get { return _recordModel; }
set {
_recordModel = value;
// populate CategoryList here from value.CategoryList
}
}
public UnknownType CategoryList { get; }
}

Use DataContext binding to assign a label the value of a public automatic property in another class

In a nutshell, I want to use DataContext binding to assign a label the value of a public automatic property in another class.
So I have a class containing a public automatic property like so:
public class MyData
{
public string DogName { get; set; }
}
My WPF form looks like this:
The CodeBehind for my WPF form is as so:
public partial class MainWindow : Window
{
private MyData myData;
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
myData = new MyData();
myData.DogName = "Lulu";
label1.DataContext = myData.DogName;
}
}
This isn't, however, changing the value of label1 to "Lulu", it just stays at "Label". What have I missed out?
Thanks
That is not exactly the common way to work with DataContexts (as you show it, MyData as a class serves no purpose at all).
Try this instead:
//C#
label1.DataContext = new MyData{ DogName = "Lulu" };
//xaml
<Label Content="{Binding Path=DogName}"/>
now, you could for instance also use MyData to contain the label's Width or so.
Another (probably most used) way is to set the parent's DataContext, and make the individual elements use it's properties:
//C#
class MyData
{
public strig DogName{ get; set; }
public strig CatName{ get; set; }
}
this.DataContext = new MyData{ DogName = "Lulu", CatName = "Fifi" };
//xaml
<Label Content="{Binding Path=DogName}"/>
<Label Content="{Binding Path=CatName}"/>

Categories