The code below executes fine when MyActionFunc is called but not when the function is in another class. MessageBox displays the correct string but it is not shown on view. What I am missing?
class ViewModel : INotifyPropertyChanged
{
public MyCommand ActionCommand
{
get;
set;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyname = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyname));
}
public ViewModel()
{
ActionCommand = new MyCommand();
ActionCommand.CanExecuteFunc = obj => true;
// ActionCommand.ExecuteFunc = MyActionFunc;
ActionCommand.ExecuteFunc = MyClass.MyActionFunc;
}
private string myname;
public string myName
{
get => myname;
set { myname = value;; OnPropertyChanged(); }
}
public void MyActionFunc(object parameter)
{
myName = "Fred";
}
}
class MyClass
{
public static void MyActionFunc(object parameter)
{
ViewModel name = new ViewModel();
name.myName = "Fred";
MessageBox.Show(name.myName);
}
}
... and the binding to the Textbox
<TextBox Name="textBox" Grid.Column="1" Grid.Row="1" Text="{Binding Path=myName, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
Related
I am new to Xamarin but i tried to use BindingContext to set image path
First i tried with
private string _imagePath;
public string ImagePath
{
get
{
return _imagePath;
}
set
{
if (_imagePath != value)
{
_imagePath = value;
OnPropertyChanged();
}
}
}
.
.
.
ImagePath = "TriangleSide_A.png";
.
.
.
<Image Source="{Binding ImagePath}" HeightRequest="300" WidthRequest="300"/>
But no luck then i tried with Auto Property
public string ImagePath {get;set;}
Thats work only with
public string ImagePath {get;} = "TriangleSide_A.png";
According to your description, I don't know how you implement INotifyPropertyChanged interface, generally, I do like this:
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The RaisePropertyChanged likes your OnPropertyChanged method, with the PropertyName that has changed. If we go to our property, we now need to update it, to raise this event, every time the property is changed.
From your code, you don't add propertyname in your OnPropertyChanged, so the ImagePath can not be updated.
Please take a look the following code:
<StackLayout>
<Image
HeightRequest="300"
Source="{Binding ImagePath}"
WidthRequest="300" />
<Button
x:Name="btn1"
Clicked="Btn1_Clicked"
Text="change image source" />
</StackLayout>
public partial class Page32 : ContentPage, INotifyPropertyChanged
{
private string _imagePath;
public string ImagePath
{
get { return _imagePath; }
set
{
if (_imagePath != value)
{
_imagePath = value;
RaisePropertyChanged("ImagePath");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public Page32()
{
InitializeComponent();
ImagePath = "a11.jpg";
this.BindingContext = this;
}
private void Btn1_Clicked(object sender, EventArgs e)
{
ImagePath = "a12.jpg";
}
}
Update:
If you want to use binding in mvvm mode, I do some code that you can take a look:
This is ImageOnClick model, contain some properties.
public class ImageOnClick:ViewModelBase
{
private string _imagePath;
public string ImagePath
{
get { return _imagePath; }
set
{
if (_imagePath != value)
{
_imagePath = value;
RaisePropertyChanged("ImagePath");
}
}
}
}
Now binding this model to contentpage
public partial class Page32 : ContentPage
{
private ImageOnClick imagemodel;
public Page32()
{
InitializeComponent();
imagemodel = new ImageOnClick() { ImagePath = "a11.jpg" };
this.BindingContext = imagemodel;
}
private void Btn1_Clicked(object sender, EventArgs e)
{
imagemodel.ImagePath = "a12.jpg";
}
}
About mvvm binding, you can also take a look:
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/xaml/xaml-basics/data-bindings-to-mvvm
Your Initial code is correct, but you can set the _imagePath to Auto Property like so:
private string _imagePath { get; set; }
public string ImagePath
{
get
{
return _imagePath;
}
set
{
if (_imagePath != value)
{
_imagePath = value;
OnPropertyChanged();
}
}
}
The reason
public string ImagePath {get;set;}
doesn't work is because you need to have the OnPropertyChanged() in the setter.
I have folder browser dialog that is bound to the SetterName
private void OnClick(object sender, RoutedEventArgs e)
{
var dialog = new FolderBrowserDialog();
var result = dialog.ShowDialog();
if (result == DialogResult.OK && AssociatedObject.DataContext != null)
{
var propertyInfo = AssociatedObject.DataContext.GetType().GetProperties(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public)
.Where(p => p.CanRead && p.CanWrite)
.First(p => p.Name.Equals(SetterName));
string dirName = new DirectoryInfo(dialog.SelectedPath).Name;
FolderName = dirName;
_fileName = System.IO.Path.GetFileName(dirName);
FileName = _fileName;
propertyInfo.SetValue(AssociatedObject.DataContext, dialog.SelectedPath, null);
}
}
And I have the properties set in the ViewModel
public class CommonUseWindowViewModel:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public Model New { get; set; }
public ICommand Build { get; set; }
public CommonUseWindowViewModel()
{
Build = new DelegateCommand(ClickedMethod);
}
protected async void ClickedMethod()
{
IsEnabled = false;
var gdbName = FolderName;
var styleFol = StyleName;
var envArray = await QueuedTask.Run(() => Geoprocessing.MakeEnvironmentArray(overwriteoutput: true));
var valueArray = await QueuedTask.Run(() => Geoprocessing.MakeValueArray(gdbName, styleFol));
string toolPath = #"c:\staging\ProBaseMapBuilder\BasemapBuilder.tbx\BasemapCreator";
var gpresult1 = await QueuedTask.Run(() => Geoprocessing.ExecuteToolAsync(toolPath, valueArray, envArray));
MessageBox.Show("All Layers Have Been Added to The Map");
IsEnabled = true;
}
private bool _isEnabled = true;
public bool IsEnabled
{
get { return _isEnabled; }
set
{
_isEnabled = value;
OnPropertyChanged("IsEnabled");
}
}
private string _folderName;
public string ShortenedFolderName => Path.GetFileName(_folderName);
public string FolderName
{
get { return _folderName; }
set
{
_folderName = value;
OnPropertyChanged("FolderName");
OnPropertyChanged(nameof(ShortenedFolderName));
}
}
private string _styleName;
public string ShortenedStyleName => Path.GetFileName(_styleName);
public string StyleName
{
get { return _styleName; }
set
{
_styleName = value;
OnPropertyChanged("StyleName");
OnPropertyChanged(nameof(ShortenedStyleName));
}
}
private void OnPropertyChanged(string propertyname)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyname));
}
private void OnPropertyChanged(PropertyChangedEventArgs args)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, args);
}
}
I would like to move the properties to a Model class but when I do the folder dialog raises an error that there is no matching setter name. I think the problem comes not knowing how to bind this to the view. I am wondering if I am referencing namespace correctly in the view
xmlns:FolderDialog="clr-namespace:BasemapCreator.Behaviors"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:Behaviors="clr-namespace:ArcGIS.Desktop.Internal.Framework.Behaviors;assembly=ArcGIS.Desktop.Framework" x:Class="BasemapCreator.CommonUseWindow"
xmlns:DataContext="clr-namespace:BasemapCreator.Models"
<TextBox x:Name="gdbName" HorizontalAlignment="Left" Height="30" Margin="56,29,0,0" Text="{Binding Model.FolderName, Mode=TwoWay}" VerticalAlignment="Top" Width="282" AllowDrop="True" Visibility="Hidden">
When I add the Model.FolderName to the text box binding I get the error coming from folder dialog.
here is my model
namespace BasemapCreator.Models
{
public class Model:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool _isEnabled = true;
public bool IsEnabled
{
get { return _isEnabled; }
set
{
_isEnabled = value;
OnPropertyChanged("IsEnabled");
}
}
private string _folderName;
public string ShortenedFolderName => Path.GetFileName(_folderName);
public string FolderName
{
get { return _folderName; }
set
{
_folderName = value;
OnPropertyChanged("FolderName");
OnPropertyChanged(nameof(ShortenedFolderName));
}
}
private string _styleName;
public string ShortenedStyleName => Path.GetFileName(_styleName);
public string StyleName
{
get { return _styleName; }
set
{
_styleName = value;
OnPropertyChanged("StyleName");
OnPropertyChanged(nameof(ShortenedStyleName));
}
}
private void OnPropertyChanged(string propertyname)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyname));
}
private void OnPropertyChanged(PropertyChangedEventArgs args)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, args);
}
}
}
I am using a viewmodel created from a button click
Button Class
namespace BasemapCreator
{
internal class ShowWindow : Button
{
private CommonUseWindow _dlg = null;
protected override void OnClick()
{
if (_dlg != null) return;
_dlg = new CommonUseWindow();
_dlg.Closing += ProWin_Closing;
_dlg.Owner = FrameworkApplication.Current.MainWindow;
_dlg.Show();
}
void ProWin_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
_dlg = null;
}
}
}
In the code behind for the view I have the DataContext set to the viewModel, can I change it and still use the viewmodel?
using ArcGIS.Desktop.Framework.Controls;
using BasemapCreator.ViewModels;
using System;
using System.Windows;
using BasemapCreator.Models;
namespace BasemapCreator
{
/// <summary>
/// Interaction logic for CommonUseWindow.xaml
/// </summary>
public partial class CommonUseWindow : ProWindow
{
private CommonUseWindowViewModel _vm = new CommonUseWindowViewModel();
public CommonUseWindow()
{
InitializeComponent();
this.DataContext = _vm;
}
}
}
Here is the code behind for the view,
In your MainWindow code behind...
using YourProjectName.FolderName;
Then in the MainWindow constructor
InitializeComponent();
//usually this is the only code behind in a view
DataContext = new ClassName();
I am new to MVVM. I found this artcle and it resolve my haf of problems.
https://social.technet.microsoft.com/wiki/contents/articles/30898.simple-navigation-technique-in-wpf-using-mvvm.aspx
However, I need to navigate from one usercontrol to another from the button click event of one usercontrol not from the button on the main window.
Usercontrol 1:
Usercontrol 2:
This is what I have tried sofar;
class Usercontrol1ViewModel : INotifyPropertyChanged
{
public ICommand navCommand { get; set; }
public Usercontrol1ViewModel()
{
navCommand = new BaseCommand(navigate);
}
private void navigate(object obj)
{
NavigationViewModel mainViewModel = new NavigationViewModel();
mainViewModel.SelectedViewModel = new Usercontrol2ViewModel();
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
NavigationView model class
Class NavigationViewModel : INotifyPropertyChanged
{
public ICommand btn1Command { get; set; }
public ICommand btn2Command { get; set; }
private object selectedViewModel;
public object SelectedViewModel
{
get { return selectedViewModel; }
set { selectedViewModel = value; OnPropertyChanged("SelectedViewModel"); }
}
public NavigationViewModel()
{
btn1Command = new BaseCommand(Opencontrl1);
btn2Command = new BaseCommand(Opencontrl2);
}
private void Opencontrl1(object obj)
{
SelectedViewModel = new Usercontrol1ViewModel();
}
private void Opencontrl2(object obj)
{
SelectedViewModel = new Usercontrol2ViewModel();
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
Main window code behind;
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new NavigationViewModel();
}
}
Can somebody guide me how to achieve this with MVVM?
Try the below code. I used the sample code in the article and I am the author of the MSDN article :)
MainWindow.xaml
<Window.Resources>
<DataTemplate DataType="{x:Type local:EmployeeViewModel}">
<local:EmployeeView/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:DepartmentViewModel}">
<local:DepartmentView/>
</DataTemplate>
</Window.Resources>
<DockPanel LastChildFill="True">
<ContentControl x:Name="Pages" DockPanel.Dock="Right" Content="{Binding SelectedViewModel}"/>
</DockPanel>
NavigationViewModel
class NavigationViewModel : INotifyPropertyChanged
{
public EmployeeViewModel EmployeeViewModel { get; set; }
public DepartmentViewModel DepartmentViewModel { get; set; }
private object selectedViewModel;
public object SelectedViewModel
{
get { return selectedViewModel; }
set { selectedViewModel = value; OnPropertyChanged("SelectedViewModel"); }
}
public NavigationViewModel()
{
SelectedViewModel = new EmployeeViewModel(OpenEmp);
}
private void OpenEmp(object obj)
{
if (obj.ToString() == "Dept")
{
SelectedViewModel = new DepartmentViewModel();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
EmployeeView
<Grid>
<StackPanel>
<TextBlock Text="This is employee view"/>
<Button Content="Navigate to Dept View" Command="{Binding Navigate}"/>
</StackPanel>
</Grid>
EmployeeViewModel
class EmployeeViewModel
{
private readonly Action<object> navigate;
public ICommand Navigate { get; set; }
public EmployeeViewModel(Action<object> navigate)
{
Navigate = new BaseCommand(OnNavigate);
this.navigate = navigate;
}
private void OnNavigate(object obj)
{
navigate.Invoke("Dept");
}
}
I have to change the value in a text box dynamically, on selecting a value from a combox box, which is present in different view. when changing the dependency property's source, the propertychangedEventHandler value is not changing, i.e it is remaining as null, so the event is not getting fired. As a result the text in the textbox is not changing. Below is the code. I have bound the text in textbox to _name property.
public partial class Details : UserControl, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string name = "";
public Details()
{
InitializeComponent();
Name = Connector.Name;
DataContext = this;
}
public string Name
{
get { return name; }
set
{
name = value; OnPropertyChanged("Name");
}
}
protected void OnPropertyChanged(string s)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(s));
}
}
}
Xaml code
<StackPanel Orientation="Vertical">
<TextBlock Text="Student Details" VerticalAlignment="Top" HorizontalAlignment="Center" FontSize="16" FontWeight="Bold"> </TextBlock>
<StackPanel Margin="0,5" Orientation="Horizontal" >
<Label MinWidth="100" MaxWidth="110">Name:</Label>
<Border BorderBrush="Gray" BorderThickness="2">
<TextBox Name="nametextbox" Text="{Binding Name,Mode=TwoWay}" Width="auto" MinWidth="100" FontWeight="Black"></TextBox>
</Border>
</StackPanel>
Is it possible that you accidentally exchanged name and _name, using name in XAML for the binding?
Usually you have a public property with a capitalized name, and a private field with a non-capitalized name, optionally prefixed with an underscore as you did.
So, you should have
public string Name {
get { return _name; }
set { _name = value; OnPropertyChanged("Name"); }
{
private string _name = "";
Please check the following:
If you're not currently binding to name instead of _name;
Either if that is or is not the case, please fix your naming convention, because it is a source of errors, and every example you'll find follow the convention I included above.
In your XAML, you are binding "Name" property and in your code, you have created _name property. So, you need to change it to "Name" property in your code.
Just change your property as per below:
private string _name = "";
public string Name
{
get { return _name; }
set {
_name = value;
OnPropertyChanged("Name");
}
}
Try this and let me know.
I have used eventaggregator for this purpose, as we need to change the text in the text box dynamically when an event in a different view is fired. Below is the C# code of both the DropView(where we select student name from a list), and DetailsView(where we display the details). I publish events in Drop.xaml.cs and subscribe to those events in Details.xaml.cs
Drop.xaml.cs
public partial class Drop : UserControl
{
private IEventAggregator iEventAggregator;
public Drop(IEventAggregator ieventaggregator)
{
InitializeComponent();
iEventAggregator = ieventaggregator;
this.DataContext = this;
var doc = XDocument.Load("C:\\Users\\srinivasaarudra.k\\Desktop\\students.xml");
var names = doc.Descendants("Name");
foreach (var item in names)
{
droplist.Items.Add(item.Value);
}
}
public string name;
public string Naam
{
get { return name; }
set { name = value;
iEventAggregator.GetEvent<Itemselectedevent>().Publish(Naam);
}
}
public string grade;
public string Grade
{
get { return grade; }
set
{
grade = value;
iEventAggregator.GetEvent<gradeevent>().Publish(Grade);
}
}
public string dept;
public string Dept
{
get { return dept; }
set
{
dept = value;
iEventAggregator.GetEvent<deptevent>().Publish(Dept);
}
}
public static string str;
public static string Str
{
get { return str; }
set {
str = value;
}
}
private void droplist_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var sel = droplist.SelectedValue;
Str=sel.ToString();
XmlDocument doc2 = new XmlDocument();
doc2.Load("C:\\Users\\srinivasaarudra.k\\Desktop\\students.xml");
var details = doc2.DocumentElement.SelectNodes("/Students/StudentDetails");
foreach (XmlNode node in details)
{
if (node.SelectSingleNode("Name").InnerText == Str)
{
Naam = node.SelectSingleNode("Name").InnerText;
Grade = node.SelectSingleNode("Grade").InnerText;
Dept = node.SelectSingleNode("Department").InnerText;
}
}
// Details det = new Details();
Details dt = new Details(iEventAggregator);
}
}
public class Itemselectedevent:Prism.Events.PubSubEvent<string>
{
}
public class gradeevent : Prism.Events.PubSubEvent<string>
{
}
public class deptevent : Prism.Events.PubSubEvent<string>
{
}
Details.xaml.cs
public partial class Details : UserControl,INotifyPropertyChanged
{
public IEventAggregator iEventAggregator;
public event PropertyChangedEventHandler PropertyChanged;
public static string name;
public static string dept;
public static string grade;
[Bindable(true)]
public string Naam
{
get { return name; }
set
{
name = value;
OnPropertyChanged("Naam");
}
}
[Bindable(true)]
public string Grade
{
get { return grade; }
set
{
grade = value; OnPropertyChanged("Grade");
}
}
[Bindable(true)]
public string Dept
{
get { return dept; }
set
{
dept = value;
OnPropertyChanged("Dept");
}
}
public Details(IEventAggregator eventaggregator)
{
InitializeComponent();
this.iEventAggregator = eventaggregator;
iEventAggregator.GetEvent<Itemselectedevent>().Subscribe((str) => { Naam = str; });
iEventAggregator.GetEvent<gradeevent>().Subscribe((str) => { Grade = str; });
iEventAggregator.GetEvent<deptevent>().Subscribe((str) => { Dept = str; });
this.DataContext = this;
}
protected void OnPropertyChanged(string s)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(s));
}
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
Application.Current.Shutdown();
}
}
My UI is not updating when more data is added to the ObservableCollection. The console output says A first chance exception of type 'System.NullReferenceException' occurred. Should I be using Inotifycollectionchanged instead? Here is some of the code:
<ListView x:Name="ListView2" ItemsSource="{Binding Source={x:Static d:GrabUserConversationModel._Conversation}, UpdateSourceTrigger=PropertyChanged}" SelectionChanged="ListView1_SelectionChanged">
UserConversationModel.cs
public class UserConversationModel : INotifyPropertyChanged
{
public UserConversationModel()
{
}
public string Name
{ get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string Obj)
{
if (PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(Obj));
}
}
}
MainWindow.xaml.cs
public partial class MainWindow
{
static GrabUserConversationModel grabUserConversationModel;
public MainWindow()
{
InitializeComponent();
...
}
static void AddData()
{
grabUserConversationModel.Conversation.Add(new UserConversationModel { Name = "TestName" });
}
GrabUserConversationModel.cs
class GrabUserConversationModel
{
public static ObservableCollection<UserConversationModel> _Conversation = new ObservableCollection<UserConversationModel>();
public ObservableCollection<UserConversationModel> Conversation
{
get { return _Conversation; }
set { _Conversation = value; }
}
...
your property ObservableCollection<UserConversationModel> Conversation is not implementing the INotifyPropertyChanged
public ObservableCollection<UserConversationModel> Conversation
{
get { return _Conversation; }
set { _Conversation = value; OnPropertyChanged("Conversation");}
}