Binding from external class in WPF - c#

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.

Related

How do I bind FontSize for WPF TextBox in XAML to a class member variable?

How do I bind FontSize for WPF TextBox in XAML to a class member variable?
I have a collection of fonts that I use through the application.
I would like to change the values of those fonts dynamically in my code behind and then have the changes reflected during runtime.
How do I achieve this?
Here is what my class definition looks like
public ClassFoo
{
public double FontSize {get; set;}
}
This is how I define my class in MainWindow.xaml.cs:
public ClassFoo SampleClass;
Here is my what my XAML looks like:
<TextBlock Name="txtSample" Text="SomeText"
FontSize="{Binding SampleClass.FontSize}"/>
Then at runtime, I instantiate the class and initialize it:
SampleClass = new ClassFoo()
{
FontSize = 16;
}
I would create it like that:
public class MainWindow : Page
{
public Foo Foo { get; set; }
public MainWindow()
{
DataContext = this;
}
}
public class Foo : INotifyPropertyChanged
{
private double _fontSize;
public double FontSize
{
get { return _fontSize; }
set
{
_fontSize = value;
OnPropertyChanged(nameof(FontSize));
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
and then call it like:
<TextBlock Name="txtSample" Text="SomeText"
FontSize="{Binding Foo.FontSize}"/>
Most likely you need a DataContext = this; in your constructor for Mainwindow.xaml.cs. You also need in Mainwindow.xaml.cs that returns SampleClass.
You can only bind to public properties so the first thing to do would be to make SampleClass a property:
public ClassFoo SampleClass { get; set; }
And if you intend to set it dynamically at runtime after the constructor of the window has returned, the window should implement the INotifyPropertyChanged interface and raise change notfications for the taget property to get automatically updated.
Finally the source of the binding must be set to the window somehow. You could set the Source property of the binding explicitly or set the DataContext of the TextBlock or any of its parent element to an instance of the window.
Try this implementation of the MainWindow class together with the XAML markup you posted:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
this.Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
SampleClass = new ClassFoo()
{
FontSize = 16
};
}
private ClassFoo _sampleClass;
public ClassFoo SampleClass
{
get { return _sampleClass; }
set { _sampleClass = value; NotifyPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}

Binding to WPF Combo Box

I have a model class that I wish to bind a combo box to. My plan was to have an object with two propertied. 1) an ObservableCollection that contains the items I want to populate the combo box with. 2) A string property that stores the value of the selected item. I cannot seem to get this to work and open to suggestions. I am Trying to follow MVVM as best as possible. The behavior I observe is an empty combo box.
The class looks like this.
public class WellListGroup : Notifier
{
private ObservableCollection<string> _headers;
public ObservableCollection<string> headers
{
get { return this._headers; }
set { this._headers = value; OnPropertyChanged("headers"); }
}
private string _selected;
public string selected
{
get { return this._selected;}
set { this._selected = value; OnPropertyChanged("selected");}
}
}
Notifier looks like:
public class Notifier : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
And my viewmodel makes a call to a data access layer that creates the following object i wish to bind to.
public class MainViewModel : Notifier
{
public static getWells gw = new getWells();
public static ObservableCollection<string> headers = gw.getHeaders();
public WellListGroup wlg = new WellListGroup {headers = headers, selected = null};
}
Data Access Layer - getHeaders()
public ObservableCollection<string> getHeaders()
{
ObservableCollection<string> vals = new ObservableCollection<string>();
WVWellModel wvm = new WVWellModel();
var properties = getProperties(wvm);
foreach (var p in properties)
{
string name = p.Name;
vals.Add(name);
}
return vals;
}
Then the view:
<ComboBox DockPanel.Dock="Top" ItemsSource = "{Binding Path = wlg.headers}" SelectedItem="{Binding Path = wlg.selected}"></ComboBox>
View Code Behind (Where the Data Context is set)
public partial class MainView : Window
{
public MainView()
{
InitializeComponent();
MainViewModel mvm = new MainViewModel();
DataContext = mvm;
}
}
App.xaml.cs
public partial class App : Application
{
private void OnStartup(object sender, StartupEventArgs e)
{
Views.MainView view = new Views.MainView();
view.Show();
}
private void APP_DispatcherUnhandledException(object sender,DispatcherUnhandledExceptionEventArgs e)
{
MessageBox.Show(e.Exception.Message);
e.Handled = true;
}
}
I have tried several iterations of this but cant for the life of me get this to work. I am presented with an empty combo box.
I am going to assume DataContext is set to MainViewModel on the view.
I think you well list group should call OnPropertyChanged
public class MainViewModel : Notifier
{
public static getWells gw = new getWells();
public static ObservableCollection<string> headers = gw.getHeaders();
private WellListGroup _wlg = new WellListGroup {headers = headers, selected = null};
public WellListGroup wlg
{
get { return _wlg; }
set { _wlg = value; OnPropertyChanged("wlg"); }
}
The combo box binding should look like this:
<ComboBox
ItemsSource = "{Binding wlg.headers}"
SelectedItem = "{Binding wlg.selected Mode=TwoWay}"
/>
If neither of those work I would make sure the MainViewModel is being instantiated and assigned to DataContext in the Page constructor or a page loaded event.
Here is a code project Tutorial that may help break down the binding process Step by Step WPF Data Binding with Comboboxes

UWP MVVM Data Binding for dummies (textbox.text from String)

Well, having a go at MVVM with UWP template 10. I have read many pages, and although everyone tries to say its really easy, I still can't make it work.
To put it into context, OCR is being run on an image, and I would like the text to be displayed in textbox automatically.
Here is my Model:
public class TextProcessing
{
private string _ocrText;
public string OcrText
{
get { return _ocrText; }
set
{
_ocrText = value;
}
}
}
Here is my ViewModel:
public class ScanPageViewModel : ViewModelBase, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private TextProcessing _ocrTextVM;
public ScanPageViewModel()
{
_ocrTextVM = new TextProcessing();
}
public TextProcessing OcrTextVM
{
get { return _ocrTextVM; }
set {
_ocrTextVM = value;
this.OnPropertyChanged("OcrTextVM");
}
}
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
Here is my View:
<TextBox x:Name="rtbOcr"
Text="{Binding OcrTextVM.OcrText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Firstly, that is not working. Could someone try to show where I am going wrong?
Then, the data is coming from a Services file, how would the Services update the value? What would be the correct code?
Thanks in advance.
Following code is cite from code.msdn (How to achieve MVVM design patterns in UWP), it will be helpful for you:
Check you code step by step.
1.ViewModel implemented interface INotifyPropertyChanged,and in property set method invoked PropertyChanged, like this:
public sealed class MainPageViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _productName;
public string ProductName
{
get { return _productName; }
set
{
_productName = value;
if (PropertyChanged != null)
{
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(nameof(ProductName)));
}
}
}
}
2.Initialize you ViewMode in you page, and set DataContext as the ViewMode, like this:
public sealed partial class MainPage : Page
{
public MainPageViewModel ViewModel { get; set; } = new MainPageViewModel();
public MainPage()
{
...
this.DataContext = ViewModel;
}
}
3.In you xaml, binding data from viewMode, like this:
<TextBox Text="{Binding Path=ProductName,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Name="ProductNameTextBox" TextChanged="ProductNameTextBox_TextChanged" />
Your OnPropertyChanged call on OcrTextVM isn't actually called in your case, since you set the value in the constructor to its backing field and bypass the property.
If you set the value via the property, it should work:
public ScanPageViewModel()
{
OcrTextVM = new TextProcessing();
}
Of course your view needs to know that ScanPageViewModel is its DataContext. Easiest way to do it is in the constructor of the code-behind of your view:
public OcrView()
{
DataContext = new ScanPageViewModel();
InitializeComponent();
}
Assuming your OCR service is returning a new TextProcessing object on usage, setting the property of OcrTextVM should suffice:
public class ScanPageViewModel : ViewModelBase, INotifyPropertyChanged
{
//...
private void GetOcrFromService()
{
//...
TextProcessing value = OcrService.Get();
OcrTextVM = value;
}
}
On a note, the OcrTextVM name doesn't really reflect what the property is doing, since it doesn't look like it's a viewmodel. Consider renaming it.
Actually, it is very easy once I manage to understand. Here is the code needed to update a TextBox.Text
In the Models:
public class DisplayText : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _text;
public string Text
{
get { return _text; }
set
{
_text = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Text)));
}
}
}
In the XAML file:
<TextBox Text="{Binding Helper.Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" ... />
In the ViewModels:
private DisplayText _helper = new DisplayText();
public DisplayText Helper
{
get { return _helper; }
set
{
_helper = value;
}
}
Then any mod from the ViewModels:
Helper.Text = "Whatever text, or method returning a string";

How to access a control that is created in a separate class file, in xaml view?

I created I dynamic label in .CS (FormEvents.cs)
DXTabItem myTabItem= new DXTabItem();
myTabItem.Header = new Label()
{
Name= "lblTabAccountHeader",
Content = "MyTab" + Convert.ToString(UserID)
};
and I want to access the label "lblTabAccountHeader" in my (AccountsDisplay.xaml) and use it as binding ElementName in placement target
<Popup x:Name="ClosingMenuPopMenuControl" PlacementTarget="{Binding ElementName=lblTabAccountHeader}" StaysOpen="False" PopupAnimation="Fade">
You can't directly access a label in xaml from a cs file. But it is possible if you implement the interface INotifyPropertyChanged and fire the event PropertyChanged when the corresponding property changes. You could achieve it with a string property.
FormEvents.cs
public class FormEvents : BindableBase
{
private string someName;
public string SomeName
{
get {return someName;}
set { SetProperty(ref someName, value);}
}
public FormEvents()
{
DXTabItem myTabItem= new DXTabItem();
myTabItem.Header = new Label()
{
Name= "lblTabAccountHeader",
Content = "MyTab" + Convert.ToString(UserID)
};
SomeName = lblTabAccountHeader.Content;
}
}
In YouView.xaml.cs
public class YourView
{
prvate FormEvents instance = new FormEvents();
public YourView()
{
instance.SomeName.PropertyChanged += EventHandler;
}
private void EventHandler(object obj)
{
TextBoxinYourView.Text = instance.SomeName;
}
}
Thus when the property changes in your class file, it will be reflected in your view also.

ListBox ItemsSource Doesn't Update

I am facing a ListBox's ItemsSource related issue. I am implementing MVVM with WPF MVVM toolkit version 0.1.
I set one ListBox itemSource to update when a user double clicks on some other element (I handled the event in the code behind and executed the command there, since binding a command to specific events are not supported). At this point through the execution of the command a new ObservableCollection of items get generated and the ListBox's ItemsSource is intended to get updated accordingly. But it is not happening at the moment. ListBox does not update dynamically. What can be the problem? I am attaching relvent code for your reference.
XAML:
List of items which is doubled click to generate the next list:
<ListBox Height="162" HorizontalAlignment="Left" Margin="10,38,0,0" Name="tablesViewList" VerticalAlignment="Top" Width="144" Background="Transparent" BorderBrush="#20EEE2E2" BorderThickness="5" Foreground="White" ItemsSource="{Binding Path=Tables}" SelectedValue="{Binding TableNameSelected, Mode=OneWayToSource}" MouseDoubleClick="tablesViewList_MouseDoubleClick"/>
Second list of items which currently does not get updated:
<ListBox Height="153" HorizontalAlignment="Left" Margin="10,233,0,0" Name="columnList" VerticalAlignment="Top" Width="144" Background="Transparent" BorderBrush="#20EEE2E2" BorderThickness="5" Foreground="White" ItemsSource="{Binding Path=Columns, Mode=OneWay}" DisplayMemberPath="ColumnDiscriptor"></ListBox>
Code Behind:
private void tablesViewList_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
MainViewModel currentViewModel = (MainViewModel)DataContext;
MessageBox.Show("Before event command is executed");
ICommand command = currentViewModel.PopulateColumns;
command.Execute(null);
MessageBox.Show(currentViewModel.TableNameSelected);
//command.Execute();
}
View Model:
namespace QueryBuilderMVVM.ViewModels
{
//delegate void Del();
public class MainViewModel : ViewModelBase
{
private DelegateCommand exitCommand;
#region Constructor
private ColumnsModel _columns;
public TablesModel Tables { get; set; }
public ControllersModel Operators { get; set; }
public ColumnsModel Columns {
get { return _columns; }
set {
_columns = value;
OnPropertyChanged("Columns");
}
}
public string TableNameSelected{get; set;}
public MainViewModel()
{
Tables = TablesModel.Current;
Operators = ControllersModel.Current;
Columns = ColumnsModel.ListOfColumns;
}
#endregion
public ICommand ExitCommand
{
get
{
if (exitCommand == null)
{
exitCommand = new DelegateCommand(Exit);
}
return exitCommand;
}
}
private void Exit()
{
Application.Current.Shutdown();
}
//Del columnsPopulateDelegate = new MainViewModel().GetColumns;
//Method to be assigned to the delegate
//Creates an object of type ColumnsModel
private void GetColumns() {
ColumnsModel.TableNameParam = TableNameSelected;
Columns = ColumnsModel.ListOfColumns;
}
private ICommand _PopulateColumns;
public ICommand PopulateColumns
{
get {
if (_PopulateColumns == null) {
_PopulateColumns = new DelegateCommand(GetColumns); // an action of type method is passed
}
return _PopulateColumns;
}
}
}
}
Model:
public class ColumnsModel : ObservableCollection<VisualQueryObject>
{
private DataSourceMetaDataRetriever dataSourceTableMetadataObject;// base object to retrieve sql data
private static ColumnsModel listOfColumns = null;
private static object _threadLock = new Object();
private static string tableNameParam = null;
public static string TableNameParam
{
get { return ColumnsModel.tableNameParam; }
set { ColumnsModel.tableNameParam = value; }
}
public static ColumnsModel ListOfColumns
{
get
{
lock (_threadLock)
if (tableNameParam != null)
listOfColumns = new ColumnsModel(tableNameParam);
return listOfColumns;
}
}
public ColumnsModel(string tableName)
{
ColumnsModel.tableNameParam = tableName;
Clear();
try
{
dataSourceTableMetadataObject = new DataSourceMetaDataRetriever();
List<ColumnDescriptionObject> columnsInTable = new List<ColumnDescriptionObject>();
columnsInTable = dataSourceTableMetadataObject.getDataTableSchema("Provider=SQLOLEDB;Data Source=.;Integrated Security=SSPI;Initial Catalog=LogiwizUser", ColumnsModel.tableNameParam);
//List<String> listOfTables = dataSourceTableMetadataObject.getDataBaseSchema("Provider=SQLOLEDB;Data Source=.;Integrated Security=SSPI;Initial Catalog=LogiwizUser");
//List<String> listOfTables = dsm.getDataBaseSchema("G:/mytestexcel.xlsx", true);
//ObservableCollection<VisualQueryObject> columnVisualQueryObjects = new ObservableCollection<VisualQueryObject>();
foreach (ColumnDescriptionObject columnDescription in columnsInTable)
{
VisualQueryObject columnVisual = new VisualQueryObject();
columnVisual.ColumnDiscriptor = columnDescription;
columnVisual.LabelType = "column";
Add(columnVisual);
}
}
catch (QueryBuilderException ex)
{
/* Label exceptionLabel = new Label();
exceptionLabel.Foreground = Brushes.White;
exceptionLabel.Content = ex.ExceptionMessage;
grid1.Children.Add(exceptionLabel);*/
}
}
}
Any help is greatly appreciated. Thanks in advance.
The setter of property Columns should raise a PropertyChanged event.
Implement INotifyPropertyChanged to do so : MSDN INotifyPropertyChanged
I guess MVVM Toolkit provides a way of doing so easily (perhaps ViewModelBase already implement the interface ...).
EDIT : Implementing INotifyPropertyChanged is not enough, you have to raise the event created by INotifyPropertyChanged. You property should look something like this :
private ColumnsModel _columns;
public ColumnsModel Columns
{
get { return _columns; }
set
{
_columns = value;
PropertyChanged("Columns");
}
}
use an observableCollection<T> instead of a List<T>
MSDN DOC:
WPF provides the ObservableCollection class, which is a built-in implementation of a data collection that exposes the INotifyCollectionChanged interface. Note that to fully support transferring data values from source objects to targets, each object in your collection that supports bindable properties must also implement the INotifyPropertyChanged interface. For more information, see Binding Sources Overview.

Categories

Resources