C#, WPF.
I am trying to implement a UserControl with elements bound to properties in an object hierarchy. I have been using this as a reference.
I have created the following minimal example. It implements three instances of the UserControl, with the textbox in each case representing a filename. A dependency property is used to permit binding. Although it executes without errors, the textboxes are blank. They should contain "test1", "test2" and "test3". What am I missing?
Main window:
<Window x:Class="CustomControlTest.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:mycontrols="clr-namespace:MyControls"
mc:Ignorable="d"
Title="MainWindow" Height="100" Width="800">
<Grid>
<StackPanel Orientation="Vertical" DataContext="{Binding ElementName=parent}">
<mycontrols:DataFileControl x:Name="Ctrl1" FName="{Binding Path=project.File1.Filename}"/>
<mycontrols:DataFileControl x:Name="Ctrl2" FName="{Binding Path=project.File2.Filename}"/>
<mycontrols:DataFileControl x:Name="Ctrl3" FName="{Binding Path=project.File3.Filename}"/>
</StackPanel>
</Grid>
</Window>
namespace CustomControlTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
Project project = new Project();
public MainWindow()
{
InitializeComponent();
project.File1.Filename = "test1";
project.File2.Filename = "test2";
project.File3.Filename = "test3";
}
}
public class Project : INotifyPropertyChanged
{
public DataFile File1 { get; set; } = new DataFile();
public DataFile File2 { get; set; } = new DataFile();
public DataFile File3 { get; set; } = new DataFile();
public event PropertyChangedEventHandler PropertyChanged;
}
public class DataFile : INotifyPropertyChanged
{
public string Filename { get; set; } = "";
public event PropertyChangedEventHandler PropertyChanged;
}
}
UserControl:
<UserControl x:Class="MyControls.DataFileControl"
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="40"
d:DesignWidth="800"
Name="TheCtrl">
<Grid Height="20">
<TextBox Name="filenameTextBox" Margin="5,0,5,0" Text="{Binding ElementName=TheCtrl, Path=FName, Mode=TwoWay}"/>
</Grid>
</UserControl>
namespace MyControls
{
public partial class DataFileControl : System.Windows.Controls.UserControl
{
public string FName
{
get { return (string)GetValue(FNameProperty); }
set { SetValue(FNameProperty, value); }
}
public static readonly DependencyProperty FNameProperty =
DependencyProperty.Register("FName", typeof(string), typeof(DataFileControl), new PropertyMetadata(null));
public DataFileControl()
{
InitializeComponent();
}
}
}
The expression
FName="{Binding Path=project.File1.Filename}"
requires a public property named project in the current DataContext, which you have not set. You should have noticed a data binding error message in the Output Window in Visual Studio when you debug the application.
Change it to
public Project project { get; } = new Project();
public MainWindow()
{
InitializeComponent();
project.File1.Filename = "test1";
project.File2.Filename = "test2";
project.File3.Filename = "test3";
DataContext = this;
}
Alternatively, use the Project instance as DataContext (and thus make it a view model)
private readonly Project project = new Project();
public MainWindow()
{
InitializeComponent();
project.File1.Filename = "test1";
project.File2.Filename = "test2";
project.File3.Filename = "test3";
DataContext = project;
}
and change the binding expressions to
FName="{Binding File1.Filename}"
Related
I'm a beginner in C# WPF programming. I'm trying to pop up a new window(dialog) to which some information is passed. The new window is to show the information on itself.
I pass some variables through the constructor of the popup window(OrderEntry), and I want the popup window to show the variables within its textblock control. Very simple task. But it's not working. I guess I did something wrong regarding the data-binding in the popup window's XAML code, but I can't figure it out. What did I do wrong? Please help.
The following is the MainWindow code-behind:
public partial class MainWindow : Window
{
public List<Order> Orders { get; set; }
public MainWindow()
{
InitializeComponent();
DataAccess da = new DataAccess();
Orders = da.GetOrders();
OrdersGrid.ItemsSource = Orders;
}
private void OrdersGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
Order item = (Order)OrdersGrid.SelectedItem;
OrderEntry dialog = new OrderEntry(item);
dialog.Show();
}
}
The next is the XAML code for OrderEntry.xaml:
<StackPanel>
<TextBlock Text="{Binding Path=_CustomerName}" />
</StackPanel>
The following is the class for the new popup window:
public partial class OrderEntry : Window
{
public Order Order { get; set; }
public string _CustomerName { get; set; }
public OrderEntry(Order order)
{
InitializeComponent();
Order = order;
_CustomerName = order.CustomerName;
}
}
Here you are trying to use the code behind as the DataContext. In most of the cases we do binding using the ViewModel class. In your case, you can follow the following.
First you need to assign a name to your view OrderEntry in OrderEntry.xaml
<Window x:Class="OrderEntry"
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"
x:Name="OrderEntry1"
Title="CodeBehindDataContext" Height="450" Width="800">
Then assign the binding as follows.
<StackPanel>
<TextBlock Text="{Binding ElementName=OrderEntry1, Path=_CustomerName}" />
</StackPanel>
Now change your OrderEntry constructor like below.
public OrderEntry(Order order)
{
Order = order;
_CustomerName = order.CustomerName;
InitializeComponent();
}
Set the datacontext of order window to itself.
Something like this
public partial class OrderEntry : Window
{
public Order Order { get; set; }
public string _CustomerName { get; set; }
public OrderEntry(Order order)
{
InitializeComponent();
DataContext = this;//datacontext to itself
Order = order;
_CustomerName = order.CustomerName;
}
}
I'm trying to use a TabControl to switch between UserControls.
I could just set the content of the tabs to the usercontrols with XAML but then it will only be bound to the view and not the viewmodel.
My VM is a Caliburn.Micro Conductor and it calls ActivateItem whenever the user switches tabs. It worked fine when I only have one usercontrol, but when I created another one the first one will not load the view.
Here's some of the code I'm using:
ShellView:
<dx:ThemedWindow x:Class="PSCServiceManager.Views.ShellView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cal="http://www.caliburnproject.org"
xmlns:dx="http://schemas.devexpress.com/winfx/2008/xaml/core"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
Title="Service Manager" WindowState="Maximized"
Height="525" Width="720">
<Grid>
<dx:DXTabControl>
<dx:DXTabItem Header="Master Teknisi">
<ContentControl x:Name="LoadMasterTechnicianView" cal:View.Model="{Binding ActiveItem}" />
</dx:DXTabItem>
<dx:DXTabItem Header="Servisan">
<ContentControl x:Name="LoadServicesView" cal:View.Model="{Binding ActiveItem}" />
</dx:DXTabItem>
</dx:DXTabControl>
</Grid>
ShellViewModel:
using Caliburn.Micro;
namespace PSCServiceManager.ViewModels
{
public class ShellViewModel : Conductor<IScreen>
{
private MasterTechnicianViewModel masterTechnicianViewModel;
private ServicesViewModel servicesViewModel;
public ShellViewModel()
{
LoadMasterTechnicianView();
}
public void LoadMasterTechnicianView()
{
ActivateItem(masterTechnicianViewModel);
}
public void LoadServicesView()
{
ActivateItem(servicesViewModel);
}
}
}
An easier/alternative way to implement this would be to create a collection of User Controls you would like to bind to the Tab Control. For example,
public interface ITabUserControl
{
string DisplayName { get; set; }
}
public class MasterTechnicianViewModel : ITabUserControl
{
public string DisplayName { get; set; } = "Master Technician";
}
public class ServicesViewModel : ITabUserControl
{
public string DisplayName { get; set; } = "Services";
}
Now in your ShellViewModel, you could create a Collection of ITabUserControl
public List<ITabUserControl> UserControls { get; set; }
public ShellViewModel()
{
UserControls = new List<ITabUserControl>();
UserControls.Add(new MasterTechnicianViewModel());
UserControls.Add(new ServicesViewModel());
}
And bind your TabControl as
<dx:DXTabControl x:Name="UserControls"/>
Now you can switch between the controls without any issues, without Activating it explicitly.
I have problems understanding the binding to a property in a subclass.
This is my code:
MainWindow.XAML
<Window x:Class="Datagrid_vs_Database.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:Datagrid_vs_Database"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<StackPanel>
<TextBlock x:Name="txtPath" Text="{Binding Path=CurrentDatabase.FullFilePath, UpdateSourceTrigger=PropertyChanged}"/>
<Button Command="{Binding openFileBrowser}" Content="New Database"/>
</StackPanel>
</Grid>
Manager.cs
public class Manager
{
private static readonly Manager instance = new Manager();
public static Manager Instance { get { return instance; } }
public IComOpenFileBrowser openFileBrowser { get; private set; }
MainWindow mainWindow;
public Database CurrentDatabase;
private Manager()
{
openFileBrowser = new IComOpenFileBrowser(SaveFileDialog);
CurrentDatabase = new Database();
mainWindow = new MainWindow();
mainWindow.DataContext = this;
mainWindow.Show();
}
private void SaveFileDialog()
{
var sfd = new SaveFileDialog();
sfd.Title = "Choose a filename and a directory to store the new database";
sfd.Filter = "SQLite Database (*.sqlite)|*.sqlite";
if (sfd.ShowDialog() == true)
{
CurrentDatabase.FullFilePath = sfd.FileName;
}
}
}
database.cs
public class Database : INotifyPropertyChanged
{
private string _FullFilePath;
public string FullFilePath {
get {
return _FullFilePath;
}
set { _FullFilePath = value;
NotifyPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
The thing is when I click on the Button, the SaveFileDialog appears, but when I type in a name and click OK, the Filename does not appear in the Textbox.
BUT: When I set mainWindow.txtPath.DataContext = CurrentDatabase; and delete the "CurrentDatabase." from the XAML code then it works.
I thought that it's correct to set the DataContext of the whole window to the manager class and then access the subclass in XAML by ..{Binding Path= CurrentDatabase.FullFilePath}..
But obviously thats not the case.
Can anybody tell my what I'm misunderstanding here?
I Know that my problem is a common one but every solution I found is not the one I really need. Here is my problem : I want to be able to switch between different usercontrol in the mainWindow. All the solution I found consist of having a menu in the main window and every button brings the corresponding userControl, like this exemple : https://rachel53461.wordpress.com/2011/12/18/navigation-with-mvvm-2/
But what i want is more like : at the begining, the mainwindows has the UserControl1 into it. In the userControl1 there would be 1 buttons who change the content of the mainWindow with a new userControl (userControl2 for instance)
the xaml of mainWindow
<Window x:Class="DataTemplateSO_Learning.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:DataTemplateSO_Learning"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type local:EmployeeViewModel}">
<local:EmployeeView/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:DepartmentViewModel}">
<local:DepartmentView/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:MenuViewModel}">
<local:MenuView/>
</DataTemplate>
</Window.Resources>
<DockPanel LastChildFill="True">
<ContentControl x:Name="Pages" DockPanel.Dock="Right" Content="{Binding SelectedViewModel}"/>
</DockPanel>
</Window>
the cs of my mainWindow :
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Pages.Content = new MenuView();
this.DataContext = new NavigationViewModel();
}
}
the xaml of my first page :
<UserControl x:Class="DataTemplateSO_Learning.MenuView"
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:DataTemplateSO_Learning"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<DockPanel LastChildFill="True">
<StackPanel x:Name="navigation" DockPanel.Dock="Left" VerticalAlignment="Center">
<Button Content="Employee" Command="{Binding EmpCommand}"></Button>
<Button Content="Department" Command="{Binding DeptCommand}"></Button>
</StackPanel>
</DockPanel>
</UserControl>
my first page View :
public partial class MenuView : UserControl
{
public MenuView()
{
InitializeComponent();
this.DataContext = new MenuViewModel();
}
}
the viewModel of my first page :
class MenuViewModel
{
public ICommand EmpCommand { get; set; }
public ICommand DeptCommand { get; set; }
public MenuViewModel()
{
EmpCommand = new BaseCommand(OpenEmp);
DeptCommand = new BaseCommand(OpenDept);
}
private void OpenEmp(object obj)
{
SelectedViewModel = new EmployeeViewModel();
}
private void OpenDept(object obj)
{
SelectedViewModel = new DepartmentViewModel();
}
}
of course he doesn't know "SelectedViewModel" because it's bind to the control of mainWindow
my navigationViewModel :
class NavigationViewModel : INotifyPropertyChanged
{
private object selectedViewModel;
public object SelectedViewModel
{
get
{
return selectedViewModel;
}
set
{
selectedViewModel = value;
OnPropertyChanged("SelectedViewModel");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
Thank you very much for your help !
You could for example inject the MenuView or MenuViewModel with a reference to the MainViewModel:
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var viewModel = new NavigationViewModel();
viewModel.SelectedViewModel = new MenuViewModel(viewModel);
this.DataContext = viewModel;
}
}
MenuViewModel.cs:
class MenuViewModel
{
public ICommand EmpCommand { get; set; }
public ICommand DeptCommand { get; set; }
private readonly NavigationViewModel _navigationViewModel;
public MenuViewModel(NavigationViewModel navigationViewModel)
{
_navigationViewModel = navigationViewModel;
EmpCommand = new BaseCommand(OpenEmp);
DeptCommand = new BaseCommand(OpenDept);
}
private void OpenEmp(object obj)
{
_navigationViewModel.SelectedViewModel = new EmployeeViewModel();
}
private void OpenDept(object obj)
{
_navigationViewModel.SelectedViewModel = new DepartmentViewModel();
}
}
MenuView.xaml.cs:
public partial class MenuView : UserControl
{
public MenuView()
{
InitializeComponent();
}
}
After being unsuccessful in databinding my Observable collection to my datagrid (Another question in this same forum), i tried to reduce the scope. Now my project has only one datagrid, one ObservableColection and one class.
But still my databinding is failing.. please help..
using System.Collections.ObjectModel;
using System.Windows;
namespace TestDatagrid
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
public class MainViewModel
{
public ObservableCollection<OptionStrike> oOs = new ObservableCollection<OptionStrike>(new OptionStrike[]
{
new OptionStrike("Put", 7500.00, 12345),
new OptionStrike("Call", 7500.00, 123),
new OptionStrike("Put", 8000.00, 23645),
new OptionStrike("Call", 8000.00,99999)
});
}
public class OptionStrike
{
public OptionStrike(string p1, double p2, int p3)
{
// TODO: Complete member initialization
this.Type = p1;
this.Strike = p2;
this.Volume = p3;
}
public string Type { get; set; }
public double Strike { get; set; }
public double Volume { get; set; }
}
}
this is my XAML..
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:TestDatagrid" x:Class="TestDatagrid.MainWindow" Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid>
<StackPanel>
<DataGrid ItemsSource="{Binding oOs}" AutoGenerateColumns="True" />
</StackPanel>
</Grid>
</Window>
You need to expose your ObservableCollection as a Property, not a Field.
public class MainViewModel
{
public ObservableCollection<OptionStrike> oOs { get; set; }
public MainViewModel()
{
oOs = new ObservableCollection<OptionStrike>(new OptionStrike[]
{
new OptionStrike("Put", 7500.00, 12345),
new OptionStrike("Call", 7500.00, 123),
new OptionStrike("Put", 8000.00, 23645),
new OptionStrike("Call", 8000.00,99999)
});
}
}
You cannot bind to fields, see here for some more information on the subject.