I have an application that look like this
The whole window is defined in the MainWindow.xaml, the green part is the content control
<ContentControl Grid.Row="1"
Grid.Column="1"
Margin="5"
Content="{Binding CurrentView}"/>
The MainViewModel looks like this:
public RelayCommand HomeViewCommand { get; set; }
public RelayCommand DetailsViewCommand { get; set; }
public HomeViewModel HomeVm { get; set; }
public DetailsViewModel DetailsVm { get; set; }
private object _currentView;
public object CurrentView
{
get { return _currentView; }
set
{
_currentView = value;
OnPropertyChanged();
}
}
public MainViewModel()
{
HomeVm = new HomeViewModel();
DetailsVm = new DetailsViewModel();
CurrentView = HomeVm;
HomeViewCommand = new RelayCommand(o =>
{
CurrentView = HomeVm;
});
}
Current and default content of the MainView is the HomeView, I already implemented the event trigger on pressing the item in the list in the HomeView. I want to know, what should I write in the HomeView method (which is triggering on the click on the item) in order to change the MainView content part to another View (DetailsView in my case). Code in my HomeViewModel:
private void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
//something here to change the currentView of the MainViewModel
}
}
Got an answer from the guy from discord server.https://discord.gg/AvnpSMDY
You could pass the MainViewModel into the constructor of another view model and assign it to a private readonly field like _mainViewModel. This way you can change the current view either by changing the CurrentView property:
_mainViewModel.CurrentView = _mainViewModel.DetailsVm;
or executing commands:
_mainViewModel.DetailsViewCommand.Execute(null);
Related
I am struggling with this for a while and I cannot figure it out. I have a button and a textBox. The textBox is linked to a property named: MessageDisplay. I want to be able to access this property and update the textBox in several places. Sadly, the PropertyChanged is null. The weird thing is that if I copy/paste the MessageDisplayModel class into the *MessageViewModel * class, it works ...
here is my code :
XAMLfile :
<Grid>
<Button Command="{Binding DisplayTextCommand}" Name="DisplayTextCommand" Margin="53,72,544.6,286" Width="Auto">Push</Button>
<TextBox Name="MessageDisplay" Text="{Binding MessageDisplay, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
</Grid>
MessageDisplayModel file
public class MessageDisplayModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _message;
public string MessageDisplay
{
get { return _message; }
set
{
this._message = value;
this.OnPropertyChanged("MessageDisplay");
}
}
public void UpdateTextBox(string output)
{
MessageDisplay = output;
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
}//class
MessageViewModel file:
public class MessageViewModel
{
private ICommand _testCommand;
public MessageDisplayModel MessageDisplaySmt = new MessageDisplayModel();
public ICommand DisplayTextCommand
{
get
{
return new DelegateCommand(DisplayMessage);
}
set
{
if (_testCommand == value) return;
_testCommand = value;
}
}
public void DisplayMessage()
{
MessageDisplaySmt.UpdateTextBox("Successfuly downloaded");
}
}//class
MainWindow file
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
MessageDisplay.DataContext = new MessageDisplayModel();
DisplayTextCommand.DataContext = new MessageViewModel();
}
}//class
I update the MessageDisplay property by using the method UpdateTextBox(string). I call this method on the click of the button. When debugging the property gets updated but when time comes to notify the UI that the property has changed, PropertyChangedEventHandler PropertyChanged has its value null ... But if I write something in the textBox, the PropertyChangedEventHandler PropertyChanged gets changed and isn't null anymore. All I want is to be able to change the textBox's property whenever I want and from anywhere I want to.
Thank you
You are using two different instances of MessageDisplayModel. You must use a shared instance.
Also the DisplayTextCommand is implemented "wrong". The set method is redundant as the property's get always returns a new instance of the ICommand.
MessageViewModel.cs
public class MessageViewModel
{
pulic MessageViewModel()
{
}
pulic MessageViewModel(MessageDisplayViewModel messageDisplayViewModel)
{
this.MessageDisplaySmt = messageDisplayViewModel;
}
public void DisplayMessage()
{
this.MessageDisplaySmt.UpdateTextBox("Successfuly downloaded");
}
public MessageDisplayViewModel MessageDisplaySmt { get; set; }
public ICommand DisplayTextCommand { get => new DelegateCommand(DisplayMessage); }
}
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// Alternatively use XAML to set the DataContext (see MainWindow.xaml). Would require a parameterless constructor.
this.DataContext = new MessageViewModel(new MessageDisplayViewModel());
}
}
MainWindow.xaml
<Window>
<!--
Alternative DataContext declaration using XAML instead of C#.
Requires a parameterless constructor for both view model objects.
-->
<Window.DataContext>
<MessageViewModel>
<MessageViewModel.MessageDisplaySmt>
<MessageDisplayViewModel />
</MessageViewModel.MessageDisplaySmt>
</MessageViewModel>
</Window.DataContext>
<StackPanel>
<Button Command="{Binding DisplayTextCommand}"
Content="Push" />
<TextBox Text="{Binding MessageDisplaySmt.MessageDisplay}" />
</StackPanel>
</Window>
I have a WPF application and I want the Start button control only enabled if they have to have specified a value in the text box for 'Download Path'.
My ViewModel contains a property for my model "ConfigurationSettings" and an ICommand implementation (CommandImp) for the button:
public class MainWindowViewModel : BaseNotifyPropertyChanged
{
private ConfigurationSettings _configurationSettings { get; set; }
public ConfigurationSettings ConfigurationSettings
{
get
{
return _configurationSettings;
}
set
{
if (_configurationSettings != value)
{
_configurationSettings = value;
RaisePropertyChanged("ConfigurationSettings");
}
}
}
public CommandImp StartCommand { get; set; } // this is an implementation of ICommand
public MainWindowViewModel()
{
StartCommand = new CommandImp(OnStart, CanStart);
_configurationSettings = new ConfigurationSettings();
_configurationSettings.PropertyChanged += delegate (object o,
PropertyChangedEventArgs args)
{
StartCommand.RaiseCanExecuteChanged(); // break point here is never reached
};
}
private bool CanStart()
{
if (!String.IsNullOrEmpty(ConfigurationSettings.DownloadPath))
{
return true;
}
return false;
}
}
In my XAML I have a Start button and the with Command = "{Binding StartCommand}".
My ConfigurationSettings class just has a string for the DownloadPath which is bound to a textbox in the XAML:
public class ConfigurationSettings : BaseNotifyPropertyChanged
{
private string _downloadPath { get; set; }
public string DownloadPath
{
get { return _downloadPath; }
set
{
if (_downloadPath != value)
{
_downloadPath = value;
RaisePropertyChanged("DownloadPath"); // break point here IS reached
}
}
}
}
When the user enters a DownloadPath, I expect it to be triggering the PropertyChanged Event, and running my delegate method defined in the ViewModel constructor.
If I move the Command Button inside the ConfigurationSettings class I can do away with event subscription and just use StartCommand.RaiseCanExecuteChanged() right beneath RaisePropertyChanged("DownloadPath");. But I don't want the ICommand as part of my Model.
How can I trigger CanStart() when one of the properties of ConfigurationSettings changes?
UPDATE:
Here is the XAML for the text box binding:
<TextBlock Text="{Binding ConfigurationSettings.DownloadPath, Mode=TwoWay, UpdateSourceTrigger=LostFocus}" TextWrapping="WrapWithOverflow" />
And the button:
<Button Content="Start" Command="{Binding StartCommand}"></Button>
I should note that the bindings are working correctly. When I update the textblock, I can see in the ViewModel that ConfigurationSettings.DownloadPath is correctly being updated.
BaseNotifyPropertyChanged is an implementation of INotifyPropertyChanged like so:
public class BaseNotifyPropertyChanged : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string property)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
}
I don't seem to be having any issues with the property changed event. I can put a break point in here and it is hit when I update the DownloadPath text box. It's when I subscribe to this PropertyChanged event in my ViewModel constructor, my delegate method isn't firing.
Hate to answer my own question but the people commenting made me think about restructuring my question - which led me to the answer before needing to make another update.
The solution was to move my event subscription inside the 'set' function for ConfigurationSettings:
private ConfigurationSettings _configurationSettings { get; set; }
public ConfigurationSettings ConfigurationSettings
{
get
{
return _configurationSettings;
}
set
{
if (_configurationSettings != value)
{
_configurationSettings = value;
_configurationSettings = new Model.ConfigurationSettings();
_configurationSettings.PropertyChanged += (o, args) =>
{
StartCommand.RaiseCanExecuteChanged();
};
RaisePropertyChanged("ConfigurationSettings");
}
}
}
The problem was where I was setting my Data Context which I did not originally suspect was at all the problem. I load the view model from an XML file on disk. And when the application is closed, I overwrite that file with the latest ViewModel.
In the constructor I was reading and setting the DataContext:
public MainWindowView()
{
InitializeComponent();
string appPath = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().CodeBase);
DataSourcePath = new Uri(System.IO.Path.Combine(appPath, DataFileName));
if (File.Exists(DataSourcePath.LocalPath))
{
XmlReader reader = XmlReader.Create(DataSourcePath.LocalPath);
DataContext = (MainWindowViewModel)serialize.Deserialize(reader);
reader.Close();
}
else
{
WriteDataViewModelToDisk(); // new empty view model written to disk
}
}
If this was the first time I ran the code, with no pre-existing file, my delegate event handler actually worked. The issue was when this code loaded a pre-existing XML file, it overwrote the ConfigurationSettings property in my view model - thus destroying the event subscription.
I have a combo box category that is binded to an ObservableCollection Categories based on tbl_Category with two properties CategoryName and CategoryDescription. Now I want to add the SelectedValue of the ComboBox to product table property Prod_Category
In my constructor :
cb.DataContext = Categories;
this.DataContext = new tbl_Product();
Combo box xaml :
<Combobox x:Name="cb" ItemSource="{Binding Categories}" DisplayMemberPath="CategoryName" SelectedValuePath="CategoryName" SelectedValue="{Binding Prod_Category,Mode=TwoWay}"/>
In my save product event :
tbl_Product prod = (tbl_Product)this.DataContext;
DataOperations.AddProduct(prod);
I get Prod_Category to null even after doing all this.
You should use SelectedItem instead of SelectedValue, refer to this.
besides that what you are willing to do wasn't so clear, I've tried to implement what you asked for based on my understanding
public partial class MainWindow : Window,INotifyPropertyChanged
{
private ObservableCollection<Category> _categories;
public ObservableCollection<Category> Categories
{
get
{
return _categories;
}
set
{
if (_categories == value)
{
return;
}
_categories = value;
OnPropertyChanged();
}
}
private Category _prod_Category ;
public Category Prod_Category
{
get
{
return _prod_Category;
}
set
{
if (_prod_Category == value)
{
return;
}
_prod_Category = value;
OnPropertyChanged();
}
}
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
Categories=new ObservableCollection<Category>()
{
new Category()
{
CategoryName = "Name1",
CategoryDescription = "Desc1"
},new Category()
{
CategoryName = "Name2",
CategoryDescription = "Desc2"
}
};
}
public void SaveButton_Click(object sender, RoutedEventArgs routedEventArgs)
{
if (Prod_Category!=null)
{
//add it to whatever you want
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Category
{
public String CategoryName { get; set; }
public String CategoryDescription { get; set; }
}
and the xaml
<StackPanel>
<ComboBox x:Name="cb" ItemsSource="{Binding Categories}" DisplayMemberPath="CategoryName" SelectedValuePath="CategoryName" SelectedItem="{Binding Prod_Category,Mode=TwoWay}"/>
<Button Content="Save" Click="SaveButton_Click"></Button>
</StackPanel>
you should consider implementing the INotifyPropertyChanged interface to notify the UI if any changes occurs in one of the binded properties
In addition to #samthedev 's provided answer, I would also recommend you change your assignment of DataContext from:
tbl_Product prod = (tbl_Product)this.DataContext;
DataOperations.AddProduct(prod);
to
tbl_Product prod = this.DataContext as tbl_Product;
if (tbl_Product != null)
{
DataOperations.AddProduct(prod);
}
This prevents any change of DataContext being accidentally bound to a different object and causing an unhandled exception due to the fact that DataContext does have tbl_Product as its base-type which can happen more often than you realise in WPF due to DataContext inheritance when someone changes something at a higher level.
Ok I'll make this very simple! Here are viewmodels :
public class ObjectsModel
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private string _objectName;
public string ObjectName
{
get
{
return _objectName;
}
set
{
if (value != _objectName)
{
_objectName = value;
PropertyChanged(this, new PropertyChangedEventArgs("ObjectName"));
}
}
}
public IEnumerable<Object> Objects {get;set;}
public ICommand AddCommand { get; private set; }
public ICommand SaveChangesCommand { get; private set; }
myDomainContext context = new myDomainContext();
public ObjectsModel()
{
objects = context.Objects;
context.Load(context.GetObjectsQuery());
}
}
public class InventoryModel
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public IEnumerable<Inventory> Inventories {get;set;}
public ICommand AddCommand { get; private set; }
public ICommand SaveChangesCommand { get; private set; }
myDomainContext context = new myDomainContext();
public ObjectsModel()
{
objects = context.Objects;
context.Load(context.GetObjectsQuery());
}
}
So what I'm trying to do is in my second form where I want to add an inventory for an object, I have to select the object in a combobox. The question is, how do I fill my combobox? Create another instance of the "ObjectsModel" in the InventoryModel? or use another "context" where I would query the other table? Or is there an easier Xaml approach? If I'm not clear, tell me I'll put more examples/code.
tx a lot!
You want to bind the contents of the combobox to a list of items provided by your ViewModel and bind the selected item to another property on the same ViewModel.
Please get into the habit of naming actual view models to end in "ViewModel", rather than "Model", so they do not clash with your other "real" models. It actually looks like you are binding directly to your business models instead of ViewModels (which is not good).
I am new to WPF and tring to learn a WPF composite application.
I have got user control which is a module which 1 label and 1 button on it.
Now I am trying to use the Command property on button which looks like
<Button Name="button1" Command="{Binding Path = GetCustomerNameCommand}">GetCustomer</Button>
In the presenter I have got
public class HelloWorldPresenter : PresenterBase<IHelloWorldView>
{
private readonly IWpfService _wpfService;
public HelloWorldViewModel CustomerViewModel { get; set; }
public ICommand GetCustomerNameCommand { get; set; }
public HelloWorldPresenter(IHelloWorldView view, IWpfService wpfService)
: base(view)
{
_wpfService = wpfService;
GetCustomerNameCommand = new DelegateCommand<string>(GetCustomerName);
View.Presenter = this;
PopulateViewModel(string.Empty);
}
private void GetCustomerName(string obj)
{
throw new NotImplementedException();
}
private void PopulateViewModel(string test)
{
CustomerViewModel = new HelloWorldViewModel { Name = _wpfService.GetCustomer() };
}
}
The thing is GetCustomerName() method is not getting executed when i click the Button
I found it, i was adding the same view 2 times which was creating the problem...