BindableBase - SetProperty does not work using the instance of the ViewModel - c#

I am trying to set the property of the MainViewModel from LoginViewModel using an instance of the MainViewModel inside LoginViewModel . But the binding in XAML does not get notified ! I set the break point inside the MainViewModel and it shows the value is being set, but if its set through instance it does not work. Can anyone help?
Below are the codes for both viewmodel. As you can see, in my loginviewmodel I set the public property using the instance of MainViewModel :
MainViewModel.Instance.Mainviewpageindex = 1;
MainViewModel
namespace DataRetrieval.ViewModels
{
public class MainViewModel : BindableBase
{
private static MainViewModel _instance = new MainViewModel();
public static MainViewModel Instance { get { return _instance; }
}
private int _mainviewpageindex;
public int Mainviewpageindex
{
get {
return _mainviewpageindex; }
set {
SetProperty(ref _mainviewpageindex, value);
// _mainviewpageindex = value;
// RaisePropertyChangedEvent("Mainviewpageindex");
}
}
public MainViewModel()
{
Mainviewpageindex = 0;
}
public DelegateCommand<object> _loginCommand;
public ICommand LoginCommand
{
get
{
_loginCommand = new DelegateCommand<object>(Login, VerifyLogin);
return _loginCommand;
}
}
private void Login(object context)
{
Mainviewpageindex = 1;
}
private bool VerifyLogin(object context)
{
return true;
}
}
LoginViewModel
namespace DataRetrieval.ViewModels
{
public class LoginViewModel : BindableBase
{
// public SecureString SecurePassword { private get; set; }
private string _uname;
public string Uname
{
get { return _uname; }
set {
SetProperty(ref _uname, value);
_loginCommand.RaiseCanExecuteChanged();
}
}
private SecureString _securePassword;
public SecureString SecurePassword
{
get { return _securePassword; }
set {
SetProperty(ref _securePassword, value);
_loginCommand.RaiseCanExecuteChanged();
}
}
public LoginViewModel()
{
}
public DelegateCommand<object> _loginCommand;
public ICommand LoginCommand
{
get
{
_loginCommand = new DelegateCommand<object>(Login, VerifyLogin);
return _loginCommand;
}
}
private void Login(object context)
{
if (SecurePassword != null || Uname != null)
{
//PrincipalContext ctx = new PrincipalContext(ContextType.Domain);
// bool validated = ctx.ValidateCredentials(Uname, SecureStringToString(SecurePassword));
MainViewModel.Instance.Mainviewpageindex = 1;
}else
{
}
}
String SecureStringToString(SecureString value)
{
IntPtr valuePtr = IntPtr.Zero;
try
{
valuePtr = Marshal.SecureStringToGlobalAllocUnicode(value);
return Marshal.PtrToStringUni(valuePtr);
}
finally
{
Marshal.ZeroFreeGlobalAllocUnicode(valuePtr);
}
}
private bool VerifyLogin(object context)
{
return true;
}
}
}
XAML:
<Window x:Class="DataRetrieval.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:views="clr-namespace:DataRetrieval.Views"
xmlns:prism="http://www.codeplex.com/prism"
xmlns:viewModels="clr-namespace:DataRetrieval.ViewModels"
Title="Data Retrieval Tool" Height="Auto" Width="900" Icon="Resources/colorful_query_mark_light_bulb_28261523_y2o_icon.ico">
<Window.DataContext>
<viewModels:MainViewModel />
</Window.DataContext>
<TabControl Name="TabControl1" BorderBrush="{x:Null}" Background="White" SelectedIndex="{Binding Mainviewpageindex, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Visibility" Value="Collapsed"/>
</Style>
</TabControl.ItemContainerStyle>
<TabItem Header="General">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*" MaxWidth="300"/>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition Height="auto" />
<RowDefinition Height="300"/>
</Grid.RowDefinitions>
<views:Login HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Column="1" Grid.Row="1" />
<TextBlock TextWrapping="Wrap" TextAlignment="Center" Text="User your OneAbbott domain user name and password to login into the system" Grid.Column="1" Grid.Row="2"/>
<Button x:Name="btnLogin" Content="Login" Margin="20" Command="{Binding LoginCommand}"/>
</Grid>
</TabItem>
<TabItem Header="Second Tab">
<StackPanel>
<TextBlock TextWrapping="Wrap" TextAlignment="Center" Text="Login Successfull" Grid.Column="1" Grid.Row="2"/>
</StackPanel>
</TabItem>
</TabControl>
</Window>

You are dealing with two different instances of your MainViewModel. The one actually hooked up to your view is constructed by the XAML parser when it hits the code that is setting your data context.
The other you are creating as a separate static object inside your viewmodel and exposing through the Instance property.
You need a different way to communicate between the ViewModel instances. Pub-Sub events in Prism are the best way to deal with this kind of scenario, although using a CompositeCommand would also work.

It seems the best way to implement the communication between ModelViews is using prism Event Aggregation !
An example of it is provided here on MSDN: https://msdn.microsoft.com/en-us/library/ff921173(v=pandp.40).aspx

Related

Why is UI not updating when OnPropertyChange is called?

I'm relatively new to MVVM and I am trying to follow the principles. I have an issue where the Model is updated externally. The model then raises an event and the ViewModel handles it, updates the relevant fields. The appropriate fields then raise the onPropertyChange event. However the UI is not updating. If I reload the page the view is updated. My Xaml:
<ItemsControl ItemsSource="{Binding EBBusBreakers}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Background="BlanchedAlmond" CornerRadius="5" Margin="0.5" BorderBrush="Black" BorderThickness="1" Padding="1">
<Border.Resources>
<Style TargetType="TextBlock">
<Style.Triggers>
<Trigger Property="Text" Value="">
<Setter Property="Visibility" Value="Collapsed" />
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="Button">
<Setter Property="Margin" Value="1" />
<Setter Property="FontSize" Value="12"/>
<Setter Property="Padding" Value="0"/>
</Style>
</Border.Resources>
<Grid Margin="0">
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Name}" HorizontalAlignment="Left" Grid.Column="0"/>
<TextBlock FontSize="12" HorizontalAlignment="Center" Grid.Row="0" Grid.Column="1" >
<TextBlock.Text>
<Binding Path="Status" PresentationTraceSources.TraceLevel="High" />
</TextBlock.Text>
</TextBlock>
<TextBlock Text="{Binding ApplicationName}" Grid.Row="1" Grid.Column="0" />
<Grid Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="0" Content="On" Command="{Binding TurnOnCommand}" HorizontalContentAlignment="Center" VerticalContentAlignment="Center"/>
<Button Grid.Column="1" Content="Off" Command="{Binding TurnOffCommand}" HorizontalContentAlignment="Center" VerticalContentAlignment="Center"/>
<Button Grid.Column="2" Content="Reset" Command="{Binding ResetCommand}" HorizontalContentAlignment="Center" VerticalContentAlignment="Center"/>
</Grid>
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="3" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
My ViewModel:
public class BreakerCollectionViewModel {
private ObservableCollection<IBreakerNodeControl> _ebBusBreakers;
public ObservableCollection<IBreakerNodeControl> EBBusBreakers
{
get => _ebBusBreakers;
set => SetProperty(ref _ebBusBreakers, value);
}
}
public class BreakerViewModel : BaseViewModel, IBreakerNodeControl
{
private readonly S201Breaker _breaker;
private string _name;
public string Name
{
get => _name;
set => SetProperty(ref _name, value);
}
private string _status;
public string Status
{
get => _status;
set => SetProperty(ref _status, value);
}
public string ApplicationName { get; set; }
public RelayCommand TurnOnCommand => new RelayCommand(param => TurnOn());
public RelayCommand TurnOffCommand => new RelayCommand(param => TurnOff());
public RelayCommand ResetCommand => new RelayCommand(param => Reset());
public BreakerViewModel(S201Breaker breaker)
{
Name = breaker.Name;
_breaker = breaker;
//Bind to property changed event to update status
_breaker.PropertyChanged += UpdateState;
}
private void UpdateState(object sender, PropertyChangedEventArgs args)
{
if (sender is S201Breaker breaker)
{
Status = breaker.State.ToString();
}
}
public void TurnOn()
{
_breaker.DesiredState = BreakerTargetStateEnum.On;
}
public void TurnOff()
{
_breaker.DesiredState = BreakerTargetStateEnum.Off;
}
public void Reset()
{
_breaker.DesiredState = BreakerTargetStateEnum.Reset;
}
}
My Model:
public class S201Breaker : INotifyPropertyChanged
{
public string Name { get; }
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyThatChanged = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyThatChanged));
}
private BreakerStateEnum _state;
public BreakerStateEnum State
{
get => _state;
set
{
_state = value;
OnPropertyChanged();
}
}
protected S201Breaker(string name)
{
Name = name;
State = BreakerStateEnum.Unknown;
}
}
EDIT: My BaseViewModel (SetProperty):
public class BaseViewModel : INotifyPropertyChanged, INotifyDataErrorInfo
{
public bool SetProperty<T>(ref T property, T value, [CallerMemberName] string name = "")
{
if (EqualityComparer<T>.Default.Equals(property, value))
{
return false;
}
property = value;
OnPropertyChanged(name);
return true;
}
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private readonly Dictionary<string, List<string>> _errorsByPropertyName = new Dictionary<string, List<string>>();
public IEnumerable GetErrors(string propertyName)
{
if (propertyName == null)
{
var allPropertyErrors = new List<string>();
foreach (var keyValuePair in _errorsByPropertyName)
{
keyValuePair.Value.ForEach(error => allPropertyErrors.Add(error));
}
return allPropertyErrors;
}
else
{
return _errorsByPropertyName.ContainsKey(propertyName) ? _errorsByPropertyName[propertyName] : null;
}
}
public bool HasErrors => _errorsByPropertyName.Any();
protected void AddError(string error, [CallerMemberName] string propertyName = null)
{
if (!_errorsByPropertyName.ContainsKey(propertyName))
{
_errorsByPropertyName[propertyName] = new List<string>();
}
if (_errorsByPropertyName[propertyName].Contains(error))
{
return;
}
_errorsByPropertyName[propertyName].Add(error);
OnErrorsChanged(propertyName);
}
protected void ClearErrors([CallerMemberName] string propertyName = null)
{
if (!_errorsByPropertyName.ContainsKey(propertyName))
{
return;
}
_errorsByPropertyName.Remove(propertyName);
OnErrorsChanged(propertyName);
}
private void OnErrorsChanged(string propertyName)
{
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
}
#region Events
public event PropertyChangedEventHandler PropertyChanged;
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
#endregion
}
EDIT: Adding interface code
public interface IBreakerNodeControl
{
string Name { get; }
string Status { get; }
string ApplicationName { get; set; }
RelayCommand TurnOnCommand { get; }
RelayCommand TurnOffCommand { get; }
RelayCommand ResetCommand { get; }
}
I have simplified the xaml and class though it should show what I am trying (rightly or wrongly) to achieve.
I have tried the following:
Removing the collection and updating just the one instance
Following the events (looks like it should be updating as normal)
Setting the tracelevel to high (this shows that the event is on triggered. Actual message: Got PropertyChanged event from BreakerViewModel)
Changing the updateSourceTrigger to onPropertyChange
Setting binding mode to oneWay
Implementing INorifyPropertyChanged in IBreakerNodeControl
I am aware that these are "simple" fixes. I am unsure though what the problem is as I can step from the model change through to the viewmodel and the viewmodel raises the event (as shown by the trace).
If there are any issues please let me know and I will amend my question to help. Any improvements are welcomed as it will help me improve!
TL;DR: Implemented INotifyPropertyChanged, change the property and the ui doesnt update until the page is refreshed even though trace says event is received.
Dicussion and prodding from Clemens in the comments made me realise that I am updating the models and subsequently the viewmodels within a task. After a bit of research I came up with the following solution by syncing UI contexts.
private void ProcessMessageQueue(TaskScheduler uiContext)
{
while (!CancelToken.IsCancellationRequested)
{
ResponseResetSignal.WaitOne();
while (CommsToLogicQueue.TryDequeue(out var incomingMessage))
{
var result = incomingMessage;
Task.Factory.StartNew(() => ProcessMessage(result), CancelToken, TaskCreationOptions.None, uiContext);
}
}
}

WPF TextBox Binding Desync

I'm having an issue where binding a textbox seems to desync from the underlying property. The first time the underlying property is updated, the textbox does not update. Then, if the underlying property is updated again, the textbox is updated with the original update. This sliding window behavior seems to continue over time. Am I doing something stupid?
I'm using .NET 4.7, vs 2017 community
MainWindow.xaml
<Window x:Class="TextBoxBugTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:TextBoxBugTest"
mc:Ignorable="d"
Title="MainWindow" Height="100" Width="225">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50*"/>
<ColumnDefinition Width="50*"/>
</Grid.ColumnDefinitions>
<TextBox Width="55" Height="23" Text="{Binding Test}"/>
<TextBox x:Name="test2" Grid.Column="1" Width="55" Height="23"/>
<Button Grid.Row="1" Grid.ColumnSpan="2" Height="23" Width="50"
Click="Button_Click" Content="update"/>
</Grid>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
MainWindowVM _vm;
private int counter = 1;
public MainWindow()
{
InitializeComponent();
_vm = new MainWindowVM();
DataContext = _vm;
test2.Text = "test";
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var current = counter++;
_vm.Test = $"test{current}";
test2.Text = $"test{current}";
}
}
MainWindowVM.cs
public class MainWindowVM : INotifyPropertyChanged
{
private string _test;
public string Test
{
get { return _test; }
set
{
if (value != _test) on_prop_changed();
_test = value;
}
}
public MainWindowVM()
{
Test = "test";
}
public event PropertyChangedEventHandler PropertyChanged;
private void on_prop_changed([CallerMemberName] string prop = "")
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
}
Yes, I am such an idiot.
private string _test;
public string Test
{
get { return _test; }
set
{
if (value != _test) on_prop_changed();
_test = value;
}
}
should be
private string _test;
public string Test
{
get { return _test; }
set
{
if (value != _test)
{
_test = value;
on_prop_changed();
}
}
}

Why are my User Settings saved only the first time this method is called?

I have a WPF MVVM app, which gets its data from a user setting which is an ObservableCollection of type Copyable (a custom class) called Copyables. Within the main view model (ClipboardAssistantViewModel), I set the source of a CollectionViewSource to Copyables. This is then bound to an ItemsControl in the main view (MainWindow). The DataTemplate for this ItemsControl is a user control, 'CopyableControl', which is essentially a button, but with attached properties that allow me to bind data and commands to it.
When a user clicks on a CopyableControl, a view model (DefineCopyableViewModel) is added to an ObservableCollection of those in ClipboardAssistantViewModel, and that collection is bound to an ItemsControl in MainWindow. The DataTemplate of this is a UserControl called DefineCopyableControl, which is set up in such a way that the current values associated with the clicked Copyable are bound to textboxes in the DefineCopyableControl for editing.
My problem: There is a method in DefineCopyableViewModel, EditCopyable(), which only works on the first run (its job is to save the user settings once any edits have taken place and the user clicks "OK"). If I click the CopyableControl and make an edit, then click "OK", then click it again, make another edit, then click "OK", then close the application and open it again, only the first edit has been saved (even though the UI was updated with the edited value both times). It seems to have something to do with the data-binding need to be "refreshed"; please see the comments in this method in the code for my findings around this.
My code is as follows:
Model:
namespace ClipboardAssistant.Models
{
public class Copyable : INotifyPropertyChanged
{
// INotifyPropertyChanged implementation
private string name;
public string Name
{
get { return name; }
set
{
if (value != name)
{
name = value;
NotifyPropertyChanged("Name");
}
}
}
private string textToCopy;
public string TextToCopy
{
get { return textToCopy; }
set
{
if (value != textToCopy)
{
textToCopy = value;
NotifyPropertyChanged("TextToCopy");
}
}
}
public Copyable() { }
public Copyable(string Name, string TextToCopy)
{
this.Name = Name;
this.TextToCopy = TextToCopy;
}
}
}
ViewModels:
namespace ClipboardAssistant.ViewModels
{
public class ClipboardAssistantViewModel : INotifyPropertyChanged
{
// INotifyPropertyChanged Implementation
public CollectionViewSource CopyablesView { get; set; }
public ObservableCollection<DefineCopyableViewModel> Definers { get; set; }
public CopyableClickCommand CopyableClickCommand { get; set; }
public ClipboardAssistantViewModel()
{
Definers = new ObservableCollection<DefineCopyableViewModel>();
CopyablesView = new CollectionViewSource();
CopyablesView.Source = Properties.Settings.Default.Copyables;
CopyableClickCommand = new CopyableClickCommand(this);
EditModeClickCommand = new EditModeClickCommand(this);
}
public void RefreshCopyables()
{
// Both these methods of refreshing appear to have the same effect.
Properties.Settings.Default.Copyables = (ObservableCollection<Copyable>)CopyablesView.Source;
CopyablesView.Source = Properties.Settings.Default.Copyables;
}
public void EditCopyable(Copyable Copyable)
{
Definers.Add(new DefineCopyableViewModel(Copyable, this));
}
}
}
namespace ClipboardAssistant.ViewModels
{
public class DefineCopyableViewModel : INotifyPropertyChanged
{
// INotifyPropertyChanged Implementation
public ClipboardAssistantViewModel MyParent { get; set; }
public Copyable Copyable { get; set; }
public DefinerOKClickCommand DefinerOKClickCommand { get; set; }
public DefineCopyableViewModel(Copyable Copyable, ClipboardAssistantViewModel MyParent)
{
this.Copyable = Copyable;
this.MyParent = MyParent;
DefinerOKClickCommand = new DefinerOKClickCommand(this);
}
public void EditCopyable()
{
// Refresh, save, no refresh, save -> doesn't save second edit.
// Save, refresh, save, no refresh -> does save second edit.
MessageBoxResult r = MessageBox.Show("Refresh?", "Refresh", MessageBoxButton.YesNo);
if (r == MessageBoxResult.Yes)
{
MyParent.RefreshCopyables();
}
// These two MessageBox methods (save and refresh) can be swapped around (see above comments).
MessageBoxResult s = MessageBox.Show("Save?", "Save", MessageBoxButton.YesNo);
if (s == MessageBoxResult.Yes)
{
Properties.Settings.Default.Save();
}
MyParent.Definers.Remove(this);
}
}
}
MainWindow:
<Window x:Class="ClipboardAssistant.Views.MainWindow" x:Name="mainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:ClipboardAssistant.ViewModels"
xmlns:ctrls="clr-namespace:ClipboardAssistant.Controls"
mc:Ignorable="d"
Title="Clipboard Assistant" Height="400" Width="700">
<Window.DataContext>
<vm:ClipboardAssistantViewModel />
</Window.DataContext>
<Grid>
<Grid Margin="15">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="20" />
<RowDefinition Height="30" />
</Grid.RowDefinitions>
<ItemsControl ItemsSource="{Binding CopyablesView.View}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ctrls:CopyableControl Copyable="{Binding}"
ClickCopyable="{Binding DataContext.CopyableClickCommand, ElementName=mainWindow}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<DockPanel Grid.Row="2">
<Button x:Name="btnEditCopyableMode" HorizontalAlignment="Left" DockPanel.Dock="Left"
Content="Edit" Margin="0,0,10,0" Command="{Binding EditModeClickCommand}" />
</DockPanel>
</Grid>
<ItemsControl ItemsSource="{Binding Definers}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ctrls:DefineCopyableControl Copyable="{Binding DataContext.Copyable}"
ClickCancel="{Binding DataContext.DefinerCancelClickCommand, ElementName=mainWindow}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
CopyableControl:
<UserControl x:Class="ClipboardAssistant.Controls.CopyableControl" x:Name="copyableControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ClipboardAssistant.Controls"
mc:Ignorable="d" d:DesignHeight="75" d:DesignWidth="200">
<Grid Width="200" Height="75">
<Button Command="{Binding ClickCopyable, ElementName=copyableControl}"
CommandParameter="{Binding Copyable, ElementName=copyableControl}"
Content="{Binding Copyable.Name, ElementName=copyableControl}"
Style="{StaticResource CopyableMainButtonStyle}" />
</Grid>
</UserControl>
namespace ClipboardAssistant.Controls
{
public partial class CopyableControl : UserControl
{
public static readonly DependencyProperty ClickCopyableProperty =
DependencyProperty.Register("ClickCopyable", typeof(ICommand), typeof(CopyableControl));
public ICommand ClickCopyable
{
get { return (ICommand)GetValue(ClickCopyableProperty); }
set { SetValue(ClickCopyableProperty, value); }
}
public static readonly DependencyProperty CopyableProperty =
DependencyProperty.Register("Copyable", typeof(Copyable), typeof(CopyableControl));
public Copyable Copyable
{
get { return (Copyable)GetValue(CopyableProperty); }
set { SetValue(CopyableProperty, value); }
}
public CopyableControl()
{
InitializeComponent();
}
}
}
DefineCopyableControl:
<UserControl x:Class="ClipboardAssistant.Controls.DefineCopyableControl" x:Name="defineCopyableControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="500">
<Grid x:Name="MainGrid" Background="Blue">
<Grid Width="200" Height="180">
<Grid.RowDefinitions>
<RowDefinition Height="30" />
<RowDefinition Height="30" />
<RowDefinition Height="10" />
<RowDefinition Height="30" />
<RowDefinition Height="30" />
<RowDefinition Height="20" />
<RowDefinition Height="30" />
</Grid.RowDefinitions>
<Label Grid.Row="0" Content="Name" Foreground="White" />
<TextBox Grid.Row="1" Text="{Binding Copyable.Name}" x:Name="tbN" />
<Label Grid.Row="3" Content="Copyable Text" Foreground="White" />
<TextBox Grid.Row="4" Text="{Binding Copyable.TextToCopy}" x:Name="tbTTC" />
<DockPanel Grid.Row="6">
<Button Width="70" Content="OK" DockPanel.Dock="Right" HorizontalAlignment="Right"
Command="{Binding DefinerOKClickCommand}"
CommandParameter="{Binding ElementName=defineCopyableControl}" />
</DockPanel>
</Grid>
</Grid>
</UserControl>
public partial class DefineCopyableControl : UserControl
{
public static readonly DependencyProperty CopyableProperty =
DependencyProperty.Register("Copyable", typeof(Copyable), typeof(DefineCopyableControl));
public Copyable Copyable
{
get { return (Copyable)GetValue(CopyableProperty); }
set { SetValue(CopyableProperty, value); }
}
public DefineCopyableControl()
{
InitializeComponent();
}
}
Commands:
namespace ClipboardAssistant.ViewModels.Commands
{
public class CopyableClickCommand : ICommand
{
public ClipboardAssistantViewModel ViewModel { get; set; }
public CopyableClickCommand(ClipboardAssistantViewModel viewModel)
{
ViewModel = viewModel;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
Copyable cp = (Copyable)parameter;
ViewModel.EditCopyable(cp);
}
}
}
namespace ClipboardAssistant.ViewModels.Commands
{
public class DefinerOKClickCommand : ICommand
{
public DefineCopyableViewModel ViewModel { get; set; }
public DefinerOKClickCommand(DefineCopyableViewModel viewModel)
{
ViewModel = viewModel;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
ViewModel.EditCopyable();
}
}
}
Settings:
namespace ClipboardAssistant.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default {
get {
return defaultInstance;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
public global::System.Collections.ObjectModel.ObservableCollection<ClipboardAssistant.Models.Copyable> Copyables {
get {
return ((global::System.Collections.ObjectModel.ObservableCollection<ClipboardAssistant.Models.Copyable>)(this["Copyables"]));
}
set {
this["Copyables"] = value;
}
}
}
}
I'm assuming you are using Visual Studio. In that case, in the My Project do you have the settings listed in the settings tab?
I ran into the same issue a while back where I tried to programatically create/save/update settings and was unsucessful until I created the setting in the Settings tab. Once that was complete I was able to make my saves as necessary.
The you just use
MySettings.Default.SettingName = value
MySettings.Default.Save()
Hope this helps!

How to show a view using Catel

I am developing a simple app with Catel. I have previously used ReactiveUI and am having a little trouble getting started with Catel. I have a basic MainWindow. In there I have a toolbar with some buttons. When a button is selected I would like to show in the bottom pane of the application a user control (based on what they selected). So far I have one basic view that has a listview in it and then a view model behind it. I need help in figuring out how to show that view when the button is selected. Thank you for your help. Here is what I have so far. As you can see, when the 'ExecutePlayersButtonCommand' in the mainviewmodel is run, i want it to show the players view. I am not sure how to get this. I can get it to come up in a popup but that is not what i want. In reactiveui I can do it with the Router.Navigate function. Not sure how to do it here.
<catel:DataWindow xmlns:Controls="clr-namespace:FootballSim.Controls;assembly=FootballSim.Controls"
xmlns:RedfoxSoftwareCustomControls="clr-namespace:RedfoxSoftwareCustomControls;assembly=RedfoxSoftwareCustomControls"
x:Class="FootballSim.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:catel="http://catel.codeplex.com"
xmlns:views="clr-namespace:FootballSim.Views"
Style="{StaticResource {x:Type Window}}"
ShowInTaskbar="True" ResizeMode="CanResize" SizeToContent="Manual"
WindowStartupLocation="Manual" WindowState="Maximized" Icon="/FootballSim;component/redfox_software_48x48.ico">
<!-- Resources -->
<catel:DataWindow.Resources>
</catel:DataWindow.Resources>
<!-- Content -->
<catel:StackGrid x:Name="LayoutRoot">
<catel:StackGrid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</catel:StackGrid.RowDefinitions>
<DockPanel>
<StackPanel DockPanel.Dock="Top">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="50" />
<RowDefinition Height="100*" />
</Grid.RowDefinitions>
<RedfoxSoftwareCustomControls:WpfCustomApplicationMenuBar x:Name="CustomMenuBar" Grid.Row="0" />
<StackPanel Grid.Row="1">
<Button HorizontalAlignment="Left" Command="{Binding PlayersButtonCommand}" Background="Transparent">
<StackPanel>
<Image Source="/FootballSim;component/Resources/People_white_48.png" Width="30"></Image>
<TextBlock Text="Players" Foreground="White" HorizontalAlignment="Center"/>
</StackPanel>
</Button>
</StackPanel>
<DockPanel LastChildFill="True" Grid.Row="2">
<ContentControl Content="{Binding contentObject}" />
<!--<views:PlayersView DataContext="{Binding PlayerProviders}" />-->
</DockPanel>
</Grid>
</StackPanel>
</DockPanel>
</catel:StackGrid>
</catel:DataWindow>
using Catel.Windows;
using FootballSim.Scripts;
using FootballSim.Services;
using FootballSim.ViewModels;
using System;
using System.ComponentModel;
using Catel.MVVM.Views;
using Catel.Windows.Data;
using Catel.MVVM;
namespace FootballSim.Views
{
public partial class MainWindow : DataWindow
{
private RedfoxMessage logger = new RedfoxMessage();
private PopulateDatabase database = new PopulateDatabase();
public MainWindow() : base(DataWindowMode.Custom)
{
try
{
InitializeComponent();
logger.LogMessage("Starting application.");
CustomMenuBar.AboutButtonClickEvent += CustomMenuBar_AboutButtonClickEvent;
}
catch (Exception e)
{
RedfoxMessage.LogMessage(e, NLog.LogLevel.Error);
}
}
private void CustomMenuBar_AboutButtonClickEvent(object sender, System.EventArgs args)
{
(DataContext as IMainWindowViewModel).AboutButtonCommand.Execute(null);
}
}
}
using Catel.Data;
using Catel.IoC;
using Catel.MVVM;
using Catel.MVVM.Services;
using FootballSim.Models;
using FootballSim.Scripts;
using FootballSim.Views;
using System.Collections.Generic;
using System.Windows;
namespace FootballSim.ViewModels
{
public interface IMainWindowViewModel
{
Command PlayersButtonCommand { get; }
Command AboutButtonCommand { get; }
List<Player> PlayerProviders { get; }
Player SelectedPlayerProvider { get; }
object ContentObject { get; }
}
/// <summary>
/// MainWindow view model.
/// </summary>
public class MainWindowViewModel : ViewModelBase, IMainWindowViewModel
{
//private readonly IUIVisualizerService _uiVisualizerService;
private PopulateDatabase _populateDatabase;
public static readonly PropertyData PlayerProvidersProperty = RegisterProperty("PlayerProviders", typeof(List<Player>));
public static readonly PropertyData SelectedPlayerProviderProperty = RegisterProperty("SelectedPlayerProvider", typeof(Player));
public Command PlayersButtonCommand { get; private set; }
public Command AboutButtonCommand { get; private set; }
public object ContentObject { get; set; }
public MainWindowViewModel() : base()
{
//var dependencyResolver = this.GetDependencyResolver();
//_uiVisualizerService = dependencyResolver.Resolve<IUIVisualizerService>();
_populateDatabase = new PopulateDatabase();
PlayerProviders = _populateDatabase.Players;
var pv = new PlayersView();
pv.DataContext = PlayerProviders;
ContentObject = pv;
PlayersButtonCommand = new Command(ExecutePlayersButtonCommand);
AboutButtonCommand = new Command(ExecuteAboutButtonCommand);
}
private void ExecutePlayersButtonCommand()
{
PlayerProviders = _populateDatabase.Players;
MessageBox.Show("Players");
}
private void ExecuteAboutButtonCommand()
{
var aboutView = new AboutView();
aboutView.ShowDialog();
}
public List<Player> PlayerProviders
{
get
{
return GetValue<List<Player>>(PlayerProvidersProperty);
}
set
{
SetValue(PlayerProvidersProperty, value);
}
}
public Player SelectedPlayerProvider
{
get
{
return GetValue<Player>(SelectedPlayerProviderProperty);
}
set
{
SetValue(SelectedPlayerProviderProperty, value);
}
}
//protected override void Initialize()
//{
// SelectedPlayerProvider = PlayerProviders[0];
//}
public override string Title { get { return "FootballSim"; } }
}
}
<catel:UserControl x:Class="FootballSim.Views.PlayersView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:catel="http://catel.codeplex.com"
xmlns:views="clr-namespace:FootballSim.Views"
xmlns:viewmodels="clr-namespace:FootballSim.ViewModels"
xmlns:models="clr-namespace:FootballSim.Models;assembly=FootballSim.Core">
<!-- Resources -->
<UserControl.Resources>
</UserControl.Resources>
<!-- Content -->
<catel:StackGrid>
<catel:StackGrid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</catel:StackGrid.RowDefinitions>
<Label Content="{Binding Title}" Foreground="White" Grid.Row="0" />
<Label Content="Here goes your real content" Foreground="White" Grid.Row="1"/>
<ListBox Grid.Row="2" ItemsSource="{Binding PlayersCollection}" SelectedItem="{Binding SelectedPlayer}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Label Content="{Binding ColumnValue}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<!--<views:PlayersView Grid.Column="1" DataContext="{Binding SelectedPlayer}" />-->
</catel:StackGrid>
</catel:UserControl>
namespace FootballSim.Views
{
using Catel.Windows.Controls;
using FootballSim.ViewModels;
public partial class PlayersView : UserControl
{
public PlayersView()
{
InitializeComponent();
}
}
}
namespace FootballSim.ViewModels
{
using Catel.MVVM;
using FootballSim.Models;
using System.Collections.Generic;
/// <summary>
/// UserControl view model.
/// </summary>
public class PlayersViewModel : ViewModelBase, IPlayersViewModel
{
public List<Player> Players { get; protected set; }
public PlayersViewModel(List<Player> players) : base()
{
Players = players;
}
public override string Title { get { return "View model title"; } }
// TODO: Register models with the vmpropmodel codesnippet
// TODO: Register view model properties with the vmprop or vmpropviewmodeltomodel codesnippets
// TODO: Register commands with the vmcommand or vmcommandwithcanexecute codesnippets
}
}
There are several ways of navigating in Catel:
IUIVisualizerService => show other dialogs
INavigationService => navigate to pages / close application / etc
Maybe it's a good idea for you to read the getting started guide of Catel.

WPF: Binding a List<class> to a ComboBox

I've been working on this problem for about 3 hours now, and I got to a dead end.
Currently I'm trying to bind a list to a ComboBox.
I have used several methods to bind the List:
Code behind:
public partial class MainWindow : Window
{
public coImportReader ir { get; set; }
public MainWindow()
{
ir = new coImportReader();
InitializeComponent();
}
private void PremadeSearchPoints(coSearchPoint sp)
{
//SearchRefPoint.DataContext = ir.SearchPointCollection;
SearchRefPoint.ItemsSource = ir.SearchPointCollection;
SearchRefPoint.DisplayMemberPath = Name;
The data was binded correctly but the DisplayMemeberPath for some reason returned the name of the class and not the name of it's member.
The XAML method returned an empty string...
<ComboBox x:Name="SearchRefPoint" Height="30" Width="324" Margin="0,10,0,0"
VerticalAlignment="Top" ItemsSource="{Binding ir.SearchPointCollection}"
DisplayMemberPath="Name">
I've also tried to fill it with a new list which I create in the MainWindow. the result was the same in both cases.
Also I've tried to create and ListCollectionView which was success, but the problem was that I could get the index of the ComboBox item. I prefer to work by an Id. For that reason I was looking for a new solution which I found at: http://zamjad.wordpress.com/2012/08/15/multi-columns-combo-box/
The problem with this example is that is not clear how the itemsource is being binded.
Edit:
To sum things up: I'm currently trying to bind a list(SearchPointsCollection) of objects(coSearchPoints) defined in a class (coImportReader).
namespace Import_Rates_Manager
{
public partial class MainWindow : Window
{
public coImportReader ir;
public coViewerControles vc;
public coSearchPoint sp;
public MainWindow()
{
InitializeComponent();
ir = new coImportReader();
vc = new coViewerControles();
sp = new coSearchPoint();
SearchRefPoint.DataContext = ir;
}
}
}
//in function....
SearchRefPoint.ItemsSource = ir.SearchPointCollection;
SearchRefPoint.DisplayMemberPath = "Name";
namespace Import_Rates_Manager
{
public class coImportReader
{
public List<coSearchPoint> SearchPointCollection = new List<coSearchPoint>();
}
}
namespace Import_Rates_Manager
{
public class coSearchPoint
{
public coSearchPoint()
{
string Name = "";
Guid Id = Guid.NewGuid();
IRange FoundCell = null;
}
}
}
This results in a filled combobox with no text
Here a simple example using the MVVM Pattern
XAML
<Window x:Class="Binding_a_List_to_a_ComboBox.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid HorizontalAlignment="Left"
VerticalAlignment="Top">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition Height="25"/>
</Grid.RowDefinitions>
<ComboBox Grid.Column="0" Grid.Row="0" ItemsSource="{Binding SearchPointCollection , UpdateSourceTrigger=PropertyChanged}"
SelectedIndex="{Binding MySelectedIndex, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding MySelectedItem, UpdateSourceTrigger=PropertyChanged}">
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Text="{Binding Id}" Grid.Row="0"/>
<TextBlock Text="{Binding Name}" Grid.Row="1"/>
<TextBlock Text="{Binding Otherstuff}" Grid.Row="2"/>
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Button Content="Bind NOW" Grid.Column="0" Grid.Row="1" Click="Button_Click"/>
<TextBlock Text="{Binding MySelectedIndex, UpdateSourceTrigger=PropertyChanged}" Grid.Column="1" Grid.Row="0"/>
<Grid Grid.Column="1" Grid.Row="1"
DataContext="{Binding MySelectedItem, UpdateSourceTrigger=PropertyChanged}">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Id}" Grid.Column="0"/>
<TextBlock Text="{Binding Name}" Grid.Column="1"/>
<TextBlock Text="{Binding SomeValue}" Grid.Column="2"/>
</Grid>
</Grid>
Code
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using Import_Rates_Manager;
namespace Binding_a_List_to_a_ComboBox
{
/// <summary>
/// Interaktionslogik für MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
DataContext = new coImportReader();
}
}
}
namespace Import_Rates_Manager
{
public class coImportReader : INotifyPropertyChanged
{
private List<coSearchPoint> myItemsSource;
private int mySelectedIndex;
private coSearchPoint mySelectedItem;
public List<coSearchPoint> SearchPointCollection
{
get { return myItemsSource; }
set
{
myItemsSource = value;
OnPropertyChanged("SearchPointCollection ");
}
}
public int MySelectedIndex
{
get { return mySelectedIndex; }
set
{
mySelectedIndex = value;
OnPropertyChanged("MySelectedIndex");
}
}
public coSearchPoint MySelectedItem
{
get { return mySelectedItem; }
set { mySelectedItem = value;
OnPropertyChanged("MySelectedItem");
}
}
#region cTor
public coImportReader()
{
myItemsSource = new List<coSearchPoint>();
myItemsSource.Add(new coSearchPoint { Name = "Name1" });
myItemsSource.Add(new coSearchPoint { Name = "Name2" });
myItemsSource.Add(new coSearchPoint { Name = "Name3" });
myItemsSource.Add(new coSearchPoint { Name = "Name4" });
myItemsSource.Add(new coSearchPoint { Name = "Name5" });
}
#endregion
#region INotifyPropertyChanged Member
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
public class coSearchPoint
{
public Guid Id { get; set; }
public String Name { get; set; }
public IRange FoundCell { get; set; }
public coSearchPoint()
{
Name = "";
Id = Guid.NewGuid();
FoundCell = null;
}
}
public interface IRange
{
string SomeValue { get; }
}
}
Here are 3 Classes:
MainWindow which set VM as his Datacontext
coImportReader the Class which presents your properties for your bindings
coSearchPoint which is just a Container for your information
IRange which is just an Interface
The Collection you are binding to needs to be a property of ir not a field.
Also try this :
public coImportReader ir { get; set; }
public <type of SearchPointCollection> irCollection { get { return ir != null ? ir.SearchPointCollection : null; } }
Bind to irCollection and see what errors you get if any.
The DisplayMemberPath should contain the property name of the elements in your collection. Assuming the elements in the SearchPointCollection are of the type SearchPoint and this class has a Property SearchPointName you should set DisplayMemberPath like this:
SearchRefPoint.DisplayMemberPath = "SearchPointName";
Edit:
In your code the class coSearchPoint has the Field Name defined in the Constructor. Name has to be a Property of the class, otherwise the Binding can't work.

Categories