I am trying to bind a checkboxs IsChecked property to a bit value stored in an sql database as Underlay.
The data context for the page is a room object, pulled strait from the db, which contains the stored bit.
newPiv.DataContext = viewModel.db.Rooms.SingleOrDefault(r => r.RoomId == roomNumber);
xaml code
<CheckBox x:Name="underlayCB" IsChecked="{Binding Underlay, Mode=TwoWay, UpdateSourceTrigger=Explicit}" />
database:
private System.Nullable<bool> _Underlay;
partial void OnUnderlayChanging(System.Nullable<bool> value);
partial void OnUnderlayChanged();
[Column(Storage = "_Underlay", DbType = "Bit")]
public System.Nullable<bool> Underlay
{
get
{
return this._Underlay;
}
set
{
if ((this._Underlay != value))
{
this.OnUnderlayChanging(value);
this.SendPropertyChanging();
this._Underlay = value;
this.SendPropertyChanged("Underlay");
this.OnUnderlayChanged();
}
}
}
I have several other bindings on the same page and other pages within the app which all work, could someone tell me what I am missing?
You have set the Binding's UpdateSourceTrigger to Explicit, this requires you to call Binding.UpdateSource() does one the methods your setter calls do that? Why not just leave it as Default or PropertyChanged?
Is INotifyPropertyChanged's event raised?
Incidentally instead of
private System.Nullable<bool> _Underlay;
You can have:
private bool? _Underlay;
Related
I am using the guide located her for reference: https://learn.microsoft.com/en-us/microsoft-edge/webview2/gettingstarted/wpf
Utilizing that guide, I was able to get WebView2 started within my application. Now I am trying to seperate the code into a ViewModel as I will have many more elements on that page. The application as a whole uses Caliburn Micro. I am able to bind everything to the ViewModel except for the WebView2 itself. Wheneer I Select the Go button it states that the WebView is null. I tried manually setting the WebView however that does not work.
BrowserView.xaml:
<Button
x:Name="ButtonGo"
Content="Go"
/>
<TextBox x:Name = "Addressbar"
/>
<wv2:WebView2 x:Name = "WebView"
Source="{Binding WebViewSource}"
/>
BrowserViewModel.cs
private WebView2 _webView;
public WebView2 WebView
{
get
{
return _webView;
}
set
{
_webView = value;
NotifyOfPropertyChange(() => WebView);
}
}
public string WebViewSource { get; set; } = "http://Google.com";
private string _addressbar;
public string Addressbar
{
get
{
return _addressbar;
}
set
{
_addressbar = value;
NotifyOfPropertyChange(() => Addressbar);
}
}
public void ButtonGo()
{
if (WebView != null && WebView.CoreWebView2 != null)
{
WebView.CoreWebView2.Navigate("https://bing.com");
}
}
No matter what I try WebView keeps coming back null and I am not able to change the page.
As aepot commented, removing the Webview property and notifying the change in the source resolved the issue. The code now looks like this for the view:
<Button x:Name="ButtonGo"
Content="Go"/>
<TextBox x:Name = "Addressbar"/>
<wv2:WebView2 x:Name = "WebView"
Source="{Binding WebViewSource}"/>
And this for the ViewModel:
public string Addressbar
{
get
{
return _addressbar;
}
set
{
_addressbar = value;
NotifyOfPropertyChange(() => Addressbar);
}
}
public void ButtonGo()
{
WebViewSource = Addressbar;
NotifyOfPropertyChange(() => WebViewSource);
}
A view model is not supposed to keep a reference to an element like WebView2. This is not MVVM and not how Caliburn.Micro works. A view model defines the source properties.
If you add a TextBlock property (you should not!) to the view model, it will also always be null just like your WebView2 property is, even if you add a TextBlock with a corresponding name in the XAML markup.
I am afraid this doesn't make much sense, both regarding MVVM in general and Caliburn.Micro in particular.
I have been taught lately when using WPF and databinding it is good practice to not name any of the fields but only to associate them with the properties in the other classes. My problem right now is how do I add the data from 3 textboxes (the user enters), save the binded information to the model which then posts the account information into the listbox on the side. I need to add the data to my model. My code from main.xaml is below:
<ListBox ItemsSource="{Binding Path=Files}" SelectedItem="{BindingPath=CurrentItem}" />
<TextBox Text="{Binding Path=bankaccount}"/>
<TextBox Text="{Binding Path=accountnumber}"/>
<TextBox Text="{Binding Path=accounttype}"/>
<Button Content="Save Data To Listbox" Click="Save_Click"/>
Now I will show my FileModel class which holds all of my properties which will be from the textboxes
private short _BankAccount;
private long _AccountNumber;
private char _AccountType;
public short bankaccount{ get { return _BankAccount;} set {_BankAccount= value; Notify("bankaccount"); } }
public long accountnumber{ get { return _AccountNumber;} set {_AccountNumber= value; Notify("accountnumber"); } }
public char accounttype{ get { return _AccountType;} set{_AccountType= value; Notify("accounttype"); } }
I use a class called ProgramModel As my middle point between the Mainpage and my FileModel page and here is that code:
public class ProgramModel : INotifyPropertyChanged
{
public ObservableCollection<FileModel> Files { get; set; }
private FileModel _currentItem;
public FileModel CurrentItem { get { return _currentItem; } set { _currentItem = value; Notify("CurrentItem"); } }
public ProgramModel()
{
Files = new ObservableCollection<FileModel>();
}
And to finish it off I have my mainpage:
internal partial class MainWindow
{
public ProgramModel Model { get; set; }
private ViewSettings _viewSettings = new ViewSettings();
public MainWindow()
{
InitializeComponent();
this.DataContext = Model = new ProgramModel();
}
private void Save_Click(object sender, RoutedEventArgs e)
{
FileModel filemodel = new FileModel();
Model.Files.Add(new FileModel( filemodel.bankaccount, filemodel.accountnumber, filemodel.accounttype));
}
I feel like I am adding to the Files Collection incorrectly from the save button event. If you guys can help me out that would be great! All 3 textboxes and the listbox are on the Main page. Let me know if you have any questions. Also, this is a learning experience so let me know if I posted too much code or not enough. Thanks!
You read the values from a new FileModel instance instead of from what is bound to the view. Code should be this:
Model.Files.Add(new FileModel
(
Model.CurrentItem.bankaccount,
Model.CurrentItem.accountnumber,
Model.CurrentItem.accounttype
));
Make sure CurrentItem is actually initialized with an instance, don't see that in your code. Also, you could use a command here and have all the relevant logic in your bound view model without the need for the event.
Also, right now you bind the current item to the selected item in the ListBox, this will modify an existing instance instead. Not sure if this is intended. If you want those fields to be for input of new instances don't bind the ListBox to it.
I'm not going to answer your question directly because implementing proper data binding will take a bit of code to do so.
Using proper data binding, it is possible to have almost no code behind on your view.cs! (Specially if you start using frameworks)
Please take a look on A Simple MVVM Example for you to follow good practice.
By following this example, you will see that you can also use data binding on buttons and other controls.
Your View Model which is ProgramModel : INotifyPropertyChanged should handle all the work (data processing).
Your model should not handle the UI update notifications thus,
public short bankaccount{ get { return _BankAccount;} set {_BankAccount= value; Notify("bankaccount"); } }
will be moved to the ProgramModel (View Model).
Save_Click method will also be converted into an ICommand and be binded to the button in view like <Button Content="Save Data To Listbox" Command="{Binding SaveExec}"/>
The point is, if you are studying data binding, you should implement it right. Hope you understand...
In the end, it is possible for your Main.cs to only be..
internal partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ProgramModel();
}
}
Just a small change and it should work . Change Your bindings as shown below for the TextBoxes.
<TextBox Text="{Binding Path=CurrentItem.bankaccount}"/>
<TextBox Text="{Binding Path=CurrentItem.accountnumber}"/>
<TextBox Text="{Binding Path=CurrentItem.accounttype}"/>
I am loving Template10 so far, very nice.
I am a little stuck though on how to bind to a Setting value on the Main Page.
I have added a new bool setting which is storing properly.
On my main page I have a Visibility binding to the setting:
Visibility="{Binding UseAmbientLightSensor, Converter={StaticResource CollapsedWhenFalseConverter}}"
This works on app start as expected, the MainPageViewModel reads the value from Settings and a grid is visible or collapsed based on that setting.
However I cannot seem to get this binding to 'listen' to the setting, if I go to the settings page and change that value, when I go back to the Main Page the visibility does not change. It only works if I restart the app.
In the vanilla Template10 install this would be akin to Binding a little logo on MainPage to the 'UseLightThemeButton' setting in the Settings page which changes based on that setting..
Okay, so I guess this is the "official" answer. But many approaches are valid. This one matches most closely to the templates. I would do it like this:
public class MainPageViewModel : ViewModelBase
{
Services.SettingService.SettingService _SettingService;
public MainPageViewModel()
{
_SettingService = Services.SettingService.SettingService.Instance;
}
public override async Task OnNavigatedToAsync(object parameter, NavigationMode mode, IDictionary<string, object> state)
{
Windows.Storage.ApplicationData.Current.DataChanged += SettingsChanged;
await Task.CompletedTask;
}
public override async Task OnNavigatedFromAsync(IDictionary<string, object> pageState, bool suspending)
{
Windows.Storage.ApplicationData.Current.DataChanged -= SettingsChanged;
await Task.CompletedTask;
}
private void SettingsChanged(Windows.Storage.ApplicationData sender, object args)
{
RaisePropertyChanged(nameof(FontSize));
}
public double FontSize { get { return _SettingService.FontSize; } }
}
With that view-model, you can easily bind to a setting (in this case FontSize).
Best of luck.
There are two possible scenarios that may not be happening:
Raise the property change event when your bool value gets updated.
Set the binding to a two way mode.
In order to do this change the binding mode of your Visibility property
Visibility="{Binding UseAmbientLightSensor, Mode=TwoWay, Converter={StaticResource CollapsedWhenFalseConverter}}"
This will tell xaml to listen to any change on the property in the view model.
Then you need to tell the View model when to let the XAML view know of its changes, if you are using Template10, then it can be done as follows:
private bool useAmbientLightSensor;
public TodoListControlViewModel UseAmbientLightSensor
{
get
{
return this.useAmbientLightSensor;
}
set
{
this.Set(ref this.useAmbientLightSensor, value);
}
}
The view model needs to extend from the ViewModelBase class which provides the Set method that raises the OnPropertyChanged event, allowing the view to know of any change in the view model.
For more info, check the INotifyPropertyChanged interface and its implementation.
I want to save and retrieve a check box value from database in mvvm model , I am using this below code .
checkbox Xaml:
<CheckBox x:Name="CbxAccess"
Margin="380,50,0,180"
FontSize="14"
IsChecked="{Binding IsActive, Mode=TwoWay}"
Checked="cbxhasAccess_Checked_1"
Unchecked="cbxhasAccess_Checked_1"
HorizontalAlignment="Left"
Width="20">
</CheckBox>
checkbox Xaml.cs :
private void cbxhasAccess_Checked_1(object sender, RoutedEventArgs e)
{
var rbtn = sender as CheckBox;
var settingsmodel = new SettingsModel();
if (rbtn.IsFocused)
{
if ((bool)rbtn.IsChecked)
{
settingsmodel.IsActive = true;
}
else
{
settingsmodel.IsActive = false;
}
}
}
model :
private bool isActive;
public bool IsActive
{
get
{
return isActive;
}
set
{
isActive = value;
RaisePropertyChanged("IsActive");
}
}
viewmodel :
SettingsModel st = new SettingsModel();
var createconfigureBatchJobsXElement = new XElement("UpgradeAccessSettings");
createconfigureBatchJobsXElement.Add(new XElement("IsActive", st.IsActive));
root.Add(createconfigureBatchJobsXElement);
in above viewmodel i am trying to get the active status and save the xelement to database , currently i am unable get the status properly , though it is checked i am getting false .
I want to retrive the status as well from database and show that in UI and do some other operations in the application based on the status(my intention is to save the value and do some operations in application based on true or false ).
please help me , thanks in adavnce
There are few problems with your code, namely:
you're creating way too many SettingsModel instances (should have one, living in view model bound to your view)
cbxhasAccess_Checked_1 are not needed as you're binding with TwoWay mode
To fix this, first and foremost you should expose settings model (or IsActive property) on your view model:
// view model
public SettingsModel Settings { get; private set; }
// view model constructor
Settings = new SettingsModel();
Then in your view your binding changes to:
<CheckBox ... IsChecked="{Binding Settings.IsActive, Mode=TwoWay}" />
Note that cbxhasAccess_Checked_1 method is not needed.
This however (exposing settings model) is not the best idiomatic way to resolve this problem with MVVM. Instead, you could keep SettingsModel private within view model and wrap around it's IsActive property:
public bool IsActive
{
get { return settingsModel.IsActive; }
set
{
if (settingsModel.IsActive != value)
{
settingsModel.IsActive
RaisePropertyChanged("IsActive");
}
}
}
Either way, important point is to have only one instance of SettingsModel within view model.
I've created a property "IsLoading" for my main view model. The idea is that a progressbar is displayed whenever this property is set to true. So far so good
The catch is, that I have a command, that calls another viewmodel (the code is there because it's a functionality from another page, but I want to be able to shortcut it as well from my main viewmodel)
So, I went ahead and modified the main property to something like this :
public const string IsLoadingPropertyName = "IsLoading";
private bool _isLoading;
public bool IsLoading
{
get
{
return _isLoading || ((ViewModelLocator)Application.Current.Resources["Locator"]).SettingsViewModel.IsLoading;
}
set
{
if (value != _isLoading)
{
_isLoading = value;
RaisePropertyChanged(IsLoadingPropertyName);
}
}
}
and the xaml
<shell:SystemTray.ProgressIndicator>
<shell:ProgressIndicator IsIndeterminate="true" IsVisible="{Binding Main.IsLoading, Source={StaticResource Locator}}" />
</shell:SystemTray.ProgressIndicator>
So, I'm saying that main view model is loading when there's something loading there, or if the settings view model is loading.
The problem is that the binding only works when setting the main view model's IsLoading property, it doesn't react when I set it in the inner IsLoading one. Both have the same property name "IsLoading". Shouldn't it be detected?
For example, in Main view model (just the execution of the command for simplicity) :
private void ExecuteRefreshCommand()
{
ViewModelLocator viewModelLocator = Application.Current.Resources["Locator"] as ViewModelLocator;
viewModelLocator.SettingsViewModel.GetCurrentLocationCommand.Execute(null);
}
and inside the settings view model :
public RelayCommand GetCurrentLocationCommand
{
get
{
Action getLocation = () =>
{
if (!NetworkInterface.GetIsNetworkAvailable())
{
return;
}
var watcher = new GeoCoordinateWatcher(GeoPositionAccuracy.Default);
watcher.PositionChanged += WatcherPositionChanged;
IsLoading = true; // settings view model "IsLoading" propertychanged raising property
watcher.Start();
};
return new RelayCommand(getLocation);
}
}
You're looking at the MainViewModel's isLoading property to determine whether to show the progressbar or not. Silverlight uses the NotifyPropertyChanged event to determine when it should reevaluate a certain property. When setting either the SettingsViewModel's IsLoading property- or the MainViewModel's property, you only raise the changedEvent for that ViewModel. You should raise the ChangedEvent for both.
A modified setter example could be (depending on the exposed methods)
set
{
if (value != _isLoading)
{
_isLoading = value;
RaisePropertyChanged(IsLoadingPropertyName);
((ViewModelLocator)Application.Current.Resources["Locator"]).SettingsViewModel.RaisePropertyChanged(IsLoadingPropertyName);
}
}
Note that many MVVM frameworks offer a functionality called Messaging which is ideal to do cross ViewModel communication without creating the strict dependency you created right now. Alternatively you can use a globally consumed IsLoading property.