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.
Related
I have been trying to create a small application to sit over a POS interface and allow the user to access some information not accessible through the standard POS software by external custom C# applications. One of these was a receipt lookup, which after a demonstration was asked to be expanded to also check online order details.
The problem I am having is with the databinding to the object which is storing the information. Originally when there was a single view and viewmodel for the function it worked correctly. After creating 2 usercontrol views to present different information, with corresponding viewmodels for each view, the new views do not show any data.
Here is the base class inherited by the Model:
using System;
using System.ComponentModel;
using System.Diagnostics;
namespace RGLibrary
{
public abstract class Observable_Object : INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
/// <summary>
/// Raised when a property on this object has a new value.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raises this object's PropertyChanged event.
/// </summary>
/// <param name="propertyName">The property that has a new value.</param>
protected virtual void OnPropertyChanged(string propertyName)
{
this.VerifyPropertyName(propertyName);
if (this.PropertyChanged != null)
{
var e = new PropertyChangedEventArgs(propertyName);
this.PropertyChanged(this, e);
}
}
#endregion // INotifyPropertyChanged Members
#region Debugging Aides
/// <summary>
/// Warns the developer if this object does not have
/// a public property with the specified name. This
/// method does not exist in a Release build.
/// </summary>
[Conditional("DEBUG")]
[DebuggerStepThrough]
public virtual void VerifyPropertyName(string propertyName)
{
// Verify that the property name matches a real,
// public, instance property on this object.
if (TypeDescriptor.GetProperties(this)[propertyName] == null)
{
string msg = "Invalid property name: " + propertyName;
if (this.ThrowOnInvalidPropertyName)
throw new Exception(msg);
else
Debug.Fail(msg);
}
}
/// <summary>
/// Returns whether an exception is thrown, or if a Debug.Fail() is used
/// when an invalid property name is passed to the VerifyPropertyName method.
/// The default value is false, but subclasses used by unit tests might
/// override this property's getter to return true.
/// </summary>
protected virtual bool ThrowOnInvalidPropertyName { get; private set; }
#endregion // Debugging Aides
}
}
The inherited class by the VMs:
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace RGLibrary
{
public class BindableBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected bool SetProperty<T>(ref T member, T val, [CallerMemberName] string propertyName = null)
{
if (Equals(member, val))
return false;
member = val;
OnPropertyChanged(propertyName);
return true;
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Here is the core VM class:
using RGLibrary;
using Store_Launcher.Model;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace Store_Launcher.ViewModel
{
public class ReceiptLookupVM: BindableBase
{
private string _ReceiptNumber;
private ReceiptLookupModel _ReceiptDetails;
public ReceiptLookupModel ReceiptDetails
{
get { return _ReceiptDetails; }
set { _ReceiptDetails = value; OnPropertyChanged("ReceiptDetails"); }
}
public RGDBConnect rms = new RGDBConnect("");
public string ReceiptNumber
{
get { return _ReceiptNumber; }
set { _ReceiptNumber = value; OnPropertyChanged("ReceiptNumber"); }
}
private OnlineOrderDetailsVM orderDetailsVM = new OnlineOrderDetailsVM();
private ReceiptDetailsVM receiptDetailsVM = new ReceiptDetailsVM();
private BindableBase _CurrentMode;
public BindableBase CurrentMode
{
get { return _CurrentMode; }
set { SetProperty(ref _CurrentMode, value); }
}
public ReceiptLookupVM()
{
ReceiptDetails = new ReceiptLookupModel();
ReceiptNumber = "";
if (System.Diagnostics.Debugger.IsAttached)
rms = new RGDBConnect(ConfigurationManager.AppSettings["rmstest"]);
else
rms = new RGDBConnect(ConfigurationManager.AppSettings["rms"]);
CheckCommand = new MyICommand<string>(OnCheck);
CurrentMode = receiptDetailsVM;
}
public MyICommand<string> CheckCommand { get; private set; }
private void OnCheck(string command)
{
ReceiptDetails.Receipt = _ReceiptNumber;
string query = "rg_launcher_receiptref_ext '" + ReceiptDetails.Receipt + "'";
try
{
DataTable results = rms.ExecuteSelect(query);
if (results.Rows.Count == 1)
{
DataRow resultRow = results.Rows[0];
if (resultRow["tran_type"].ToString() == "SALE")
{
ReceiptDetails.SaleCode = resultRow["sale_code"].ToString();
ReceiptDetails.Customer = resultRow["name"].ToString();
ReceiptDetails.CustomerID = resultRow["customer_id"].ToString();
ReceiptDetails.Items = resultRow["units"].ToString();
ReceiptDetails.Value = resultRow["value"].ToString();
ReceiptDetails.Stylist = resultRow["stylist"].ToString();
ReceiptDetails.TransactionType = ReceiptLookupModel.TransType.RetailOrder;
receiptDetailsVM.UpdateDetails(ReceiptDetails);
CurrentMode = receiptDetailsVM;
}
else if (resultRow["tran_type"].ToString() == "WEB ORDER")
{
ReceiptDetails.SaleCode = resultRow["sale_code"].ToString();
ReceiptDetails.ReceiptNumber = resultRow["receipt_ref"].ToString();
ReceiptDetails.Customer = resultRow["name"].ToString();
ReceiptDetails.CustomerID = resultRow["customer_id"].ToString();
ReceiptDetails.Items = resultRow["units"].ToString();
ReceiptDetails.Value = resultRow["value"].ToString();
ReceiptDetails.TransactionType = ReceiptLookupModel.TransType.OnlineOrder;
orderDetailsVM.UpdateDetails(ReceiptDetails);
CurrentMode = orderDetailsVM;
}
else
{
MessageBox.Show(
"Unable to determine the transaction type for this number. Please contact IT for assistance",
"Receipt Lookup: Unknown order number",
MessageBoxButton.OK,
MessageBoxImage.Warning);
ReceiptDetails = new ReceiptLookupModel();
receiptDetailsVM.UpdateDetails(ReceiptDetails);
CurrentMode = receiptDetailsVM;
}
}
else if (results.Rows.Count == 0)
{
MessageBox.Show(
"Unable to find this receipt number in the system. Please make sure that the receipt number has been entered correctly.",
"Receipt Lookup: Unable to find sale",
MessageBoxButton.OK,
MessageBoxImage.Exclamation);
ReceiptDetails = new ReceiptLookupModel();
receiptDetailsVM.UpdateDetails(ReceiptDetails);
CurrentMode = receiptDetailsVM;
}
else
{
MessageBox.Show(
"An error has occured and the system is unable to properly locate this receipt number in the system. Please contact IT for assistance",
"Receipt Lookup: Unable to find sale",
MessageBoxButton.OK,
MessageBoxImage.Warning);
ReceiptDetails = new ReceiptLookupModel();
receiptDetailsVM.UpdateDetails(ReceiptDetails);
CurrentMode = receiptDetailsVM;
}
}
catch (Exception e)
{
MessageBox.Show(
e.Message,
"Receipt Lookup: An error has occurred",
MessageBoxButton.OK,
MessageBoxImage.Warning);
MessageBox.Show(
"An error has occured and the system is unable to properly locate this receipt number in the system. Please check to make sure your computer is currently connected to the internet. Contact IT for further assistance",
"Receipt Lookup: Unable to lookup receipt number",
MessageBoxButton.OK,
MessageBoxImage.Exclamation);
ReceiptDetails = new ReceiptLookupModel();
receiptDetailsVM.UpdateDetails(ReceiptDetails);
CurrentMode = receiptDetailsVM;
}
}
}
}
Here is the corresponding view:
<Window x:Class="Store_Launcher.Views.ReceiptLookupView"
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:Store_Launcher.Views"
xmlns:viewmodel="clr-namespace:Store_Launcher.ViewModel"
mc:Ignorable="d"
Title="Rodd & Gunn Launcher: Receipt Lookup" Height="195" Width="450"
ShowInTaskbar="True" ResizeMode="NoResize" Topmost="True" >
<Window.DataContext>
<viewmodel:ReceiptLookupVM/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type viewmodel:OnlineOrderDetailsVM}">
<local:OnlineOrderDetailsView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewmodel:ReceiptDetailsVM}">
<local:ReceiptDetailsView/>
</DataTemplate>
</Window.Resources>
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.Close"
Executed="CloseCommandHandler"/>
</Window.CommandBindings>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="AUTO"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="AUTO"/>
<RowDefinition Height="AUTO"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0"
Grid.Column="0"
Orientation="Horizontal"
Margin="5"
VerticalAlignment="Center">
<TextBlock Text="Receipt Number: "/>
<TextBox Width="100"
Text="{Binding ReceiptNumber, Mode=TwoWay}"/>
</StackPanel>
<!-- New User Control XAML to switch between brick and mortar, and online order modes -->
<UserControl
Margin="5"
Height="115"
Width="230"
Grid.Column="1">
<ContentControl Content="{Binding CurrentMode}"/>
</UserControl>
<!-- Original Grid XAML -->
<!--<Grid Grid.Row="0"
Grid.Column="1"
Margin="5"
DataContext="{Binding ReceiptDetails}">
<Grid.RowDefinitions>
<RowDefinition Height="AUTO"/>
<RowDefinition Height="AUTO"/>
<RowDefinition Height="AUTO"/>
<RowDefinition Height="AUTO"/>
<RowDefinition Height="AUTO"/>
<RowDefinition Height="AUTO"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="AUTO"/>
<ColumnDefinition Width="AUTO"/>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0"
Grid.Column="0"
Text="Sale Code: "/>
<TextBlock
Grid.Row="0"
Grid.Column="1"
Text="{Binding SaleCode}"/>
<TextBlock
Grid.Row="1"
Grid.Column="0"
Text="Customer ID: "/>
<TextBlock
Grid.Row="1"
Grid.Column="1"
Text="{Binding CustomerID}"/>
<TextBlock
Grid.Row="2"
Grid.Column="0"
Text="Customer: "/>
<TextBlock
Grid.Row="2"
Grid.Column="1"
Text="{Binding Customer}"/>
<TextBlock
Grid.Row="3"
Grid.Column="0"
Text="Items: "/>
<TextBlock
Grid.Row="3"
Grid.Column="1"
Text="{Binding Items}"/>
<TextBlock
Grid.Row="4"
Grid.Column="0"
Text="Value: "/>
<TextBlock
Grid.Row="4"
Grid.Column="1"
Text="{Binding Value}"/>
<TextBlock
Grid.Row="5"
Grid.Column="0"
Text="Stylist: "/>
<TextBlock
Grid.Row="5"
Grid.Column="1"
Text="{Binding Stylist}"/>
</Grid>-->
<StackPanel
Grid.Column="0"
Grid.Row="1"
Grid.ColumnSpan="2"
Orientation="Horizontal"
HorizontalAlignment="Center">
<Button
Width="100"
Height="20"
Margin="5"
Content="CHECK"
Command="{Binding CheckCommand}"/>
<Button
Width="100"
Height="20"
Margin="5"
Content="CLOSE"
Command="ApplicationCommands.Close"/>
</StackPanel>
</Grid>
</Window>
Here is the order details model:
using RGLibrary;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Store_Launcher.Model
{
public class ReceiptLookupModel: Observable_Object
{
private string _Receipt;
private string _SaleCode;
private string _ReceiptNumber;
private string _CustomerID;
private string _Customer;
private string _Items;
private string _Value;
private string _Stylist;
private TransType? _TransactionType;
public string Receipt
{
get { return _Receipt = (_Receipt ?? ""); }
set { _Receipt = value; OnPropertyChanged("Receipt"); }
}
public string SaleCode
{
get { return _SaleCode = (_SaleCode ?? ""); }
set { _SaleCode = value; OnPropertyChanged("SaleCode"); }
}
public string ReceiptNumber
{
get { return _ReceiptNumber = (_ReceiptNumber ?? ""); }
set { _ReceiptNumber = value; OnPropertyChanged("ReceiptNumber"); }
}
public string CustomerID
{
get { return _CustomerID = (_CustomerID ?? ""); }
set { _CustomerID = value; OnPropertyChanged("CustomerID"); }
}
public string Customer
{
get { return _Customer = (_Customer ?? ""); }
set { _Customer = value; OnPropertyChanged("Customer"); }
}
public string Items
{
get { return _Items = (_Items ?? "0"); }
set { _Items = value; OnPropertyChanged("Items"); }
}
public string Value
{
get { return _Value = (_Value ?? "$0.00"); }
set { _Value = value; OnPropertyChanged("Value"); }
}
public string Stylist
{
get { return _Stylist = (_Stylist ?? ""); }
set { _Stylist = value; OnPropertyChanged("Stylist"); }
}
public TransType? TransactionType
{
get { return _TransactionType = (_TransactionType ?? TransType.None); }
set { _TransactionType = value; OnPropertyChanged("TransactionType"); }
}
public enum TransType
{
OnlineOrder,
RetailOrder,
None
}
}
}
The online orders viewmodel:
using RGLibrary;
using Store_Launcher.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace Store_Launcher.ViewModel
{
public class OnlineOrderDetailsVM: BindableBase
{
private ReceiptLookupModel _OrderDetails;
public ReceiptLookupModel OrderDetails
{
get { return _OrderDetails; }
set { _OrderDetails = value; OnPropertyChanged("OrderDetails"); }
}
public OnlineOrderDetailsVM()
{
OrderDetails = new ReceiptLookupModel();
}
public void UpdateDetails(ReceiptLookupModel SQLData)
{
ReceiptLookupModel _data = new ReceiptLookupModel();
_data.Customer = SQLData.Customer;
_data.CustomerID = SQLData.CustomerID;
_data.Items = SQLData.Items;
_data.Receipt = SQLData.Receipt;
_data.ReceiptNumber = SQLData.Receipt;
_data.SaleCode = SQLData.SaleCode;
_data.Stylist = SQLData.Stylist;
_data.TransactionType = SQLData.TransactionType;
_data.Value = SQLData.Value;
OrderDetails = _data;
}
}
}
Here is the order details view:
<UserControl x:Name="OnlineOrderDetailsUC"
x:Class="Store_Launcher.Views.OnlineOrderDetailsView"
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:Store_Launcher.Views"
xmlns:viewmodel="clr-namespace:Store_Launcher.ViewModel"
mc:Ignorable="d"
d:DesignHeight="115" d:DesignWidth="230">
<UserControl.DataContext>
<viewmodel:OnlineOrderDetailsVM/>
</UserControl.DataContext>
<Grid Grid.Row="0"
Grid.Column="1"
Margin="5"
DataContext="{Binding OrderDetails, NotifyOnSourceUpdated=True}">
<Grid.RowDefinitions>
<RowDefinition Height="AUTO"/>
<RowDefinition Height="AUTO"/>
<RowDefinition Height="AUTO"/>
<RowDefinition Height="AUTO"/>
<RowDefinition Height="AUTO"/>
<RowDefinition Height="AUTO"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="AUTO"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0"
Grid.Column="0"
Text="Sale Code: "/>
<TextBlock
Grid.Row="0"
Grid.Column="1"
Text="{Binding SaleCode}"/>
<TextBlock
Grid.Row="1"
Grid.Column="0"
Text="Receipt No: "/>
<TextBlock
Grid.Row="1"
Grid.Column="1"
Text="{Binding ReceiptNumber}"/>
<TextBlock
Grid.Row="2"
Grid.Column="0"
Text="Customer ID: "/>
<TextBlock
Grid.Row="2"
Grid.Column="1"
Text="{Binding CustomerID}"/>
<TextBlock
Grid.Row="3"
Grid.Column="0"
Text="Customer: "/>
<TextBlock
Grid.Row="3"
Grid.Column="1"
Text="{Binding Customer}"/>
<TextBlock
Grid.Row="4"
Grid.Column="0"
Text="Items: "/>
<TextBlock
Grid.Row="4"
Grid.Column="1"
Text="{Binding Items}"/>
<TextBlock
Grid.Row="5"
Grid.Column="0"
Text="Value: "/>
<TextBlock
Grid.Row="5"
Grid.Column="1"
Text="{Binding Value}"/>
</Grid>
</UserControl>
The thing I am confused about is that in the receiptLookup view, when I use the commented out grid instead of usercontrol section that binds to the model object in the receiptLookup viewmodel correctly but the usercontrol binding does not seem to work.
I have tried a number of things to resolve this. Originally instead of using a method inside the orderdetails viewmodel to set the model's object value I was just setting it to be the same as the one generated in the receiptlookup viewmodel.
In addition I have tried to use diag:PresentationTraceSources.TraceLevel=High in the binding to diagnose if there is a binding error based on other questions that have been asked previously. I think that it is binding to something because there is no error about the binding failing, and it lists a hash for an object that is binding to.
Further to this I have tried to remove the datacontext from both the orderdetails view's usercontrol and also from the datagrid's datacontext, and specify it in the binding path but that did not change any results either.
Another question was given the advice to add an x:name to their views, so I have done the same without any changes. I have also tried adding NotifyOnSourceUpdate=True to each textblock's binding and Mode=TwoWay but neither on their own or the combination of both has helped.
I have used a similar usercontrol sub-section of the main view in a few previous applications at this point so I am really quite stuck as to why it is not working in this case. I have tried to add all the relevant code but if you need me to list anything else please let me know in your comment.
If you have a UserControl in a DataTemplate like
<DataTemplate DataType="{x:Type viewmodel:OnlineOrderDetailsVM}">
<local:OnlineOrderDetailsView/>
</DataTemplate>
the UserControl is supposed to inherit its DataContext from its parent object, which is a ContentControl or a ContentPresenter here. The DataContext holds an instance of the type specified by the DataTemplate's DataType property (i.e. the Content of the ContentControl or ContentPresenter).
This property value inheritance only works if you do not explicity set the property, as in
<UserControl.DataContext>
<viewmodel:OnlineOrderDetailsVM/>
</UserControl.DataContext>
This DataContext value has higher precedence than the inherited value. The result is that the UserControl in the DataTemplate always binds to its private DataContext object, but never to the one provided by the template.
So just remove the DataContext assignment from the UserControl's XAML. Controls should never explicitly set their own DataContext, hence never have private view model objects.
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!
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
I'm trying to let the user load an image in the view.
My main view:
<UserControl x:Class="TestApplication.Views.MainView"
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">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="0.3*" />
</Grid.ColumnDefinitions>
<Image
Grid.Column="0"
Stretch="Uniform"
x:Name="Preview">
<Image.Source>
<BitmapImage UriSource="pack://application:,,,/Resources/placeholder.jpg" />
</Image.Source>
</Image>
<ContentControl
Grid.Column="1"
x:Name="ImageManagement" />
</Grid>
</UserControl>
My main viewmodel:
namespace TestApplication.ViewModels
{
using System;
using System.ComponentModel.Composition;
using System.Windows.Controls;
using Caliburn.Micro;
using PropertyChanged;
using TestApplication.Events;
[ImplementPropertyChanged]
[Export(typeof(MainViewModel))]
public class MainViewModel : PropertyChangedBase, IHandle<FileSelectedEvent>
{
private readonly IEventAggregator events;
[ImportingConstructor]
public MainViewModel(IEventAggregator events)
{
this.events = events;
this.events.Subscribe(this);
this.ImageManagement = new ImageManagementViewModel(events);
}
public ImageManagementViewModel ImageManagement { get; set; }
public Image Preview { get; set; }
public void Handle(FileSelectedEvent message)
{
// load the selected image
}
}
}
The image placeholder isn't displayed, and snoop doesn't even see it.
Furthermore, when loading an image by instantiating a BitmapImage and setting it to the Preview.Source in the Handle method... The Preview property is null, and if I initialize it first, it's never displayed either.
The PropertyChanged is handled through Fody.
Thanks to #Will and #mvermef, I found my answer.
The placeholder URL was not invalid, but it does work without the pack:// part. I like it, though, because then it's explicit that it's a resource and not a file.
The real problem was that, indeed, Caliburn.Micro wasn't actually binding to the control, but to the Source property. Switching to this solved the issue:
<Image
Grid.Column="0"
Stretch="Uniform"
Source="{Binding PreviewUrl}" />
and the viewmodel:
[ImportingConstructor]
public MainViewModel(IEventAggregator events)
{
this.events = events;
this.events.Subscribe(this);
this.ImageManagement = new ImageManagementViewModel(events);
this.PreviewUrl = "pack://application:,,,/Resources/placeholder.jpg";
}
public String PreviewUrl { get; set; }
public void Handle(FileSelectedEvent message)
{
this.PreviewUrl = message.FilePath;
}
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.