0

I have written a WPF application. It has the following views:

  1. AddStockView.cs (It is used to add stocks)
  2. StockBox.cs (Represents a combo box that displays all the stocks).
  3. View.cs (for MainWindow.xaml)

Of course, each view has an xaml file. Both views should be linked to the ViewModel class. To make sure that the DataContext of both views points to the same ViewModel object, I passed it as a constructor parameter. I am compiling the application in Visual Studio code. The compile seems to work however no window start. Probably my approach is wrong. I wanted to ask you what I need to change to make the application behave correctly. App.xaml.cs

namespace analyser
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        // Statische SQLiteConnection-Instanz für die gesamte Anwendung
        public static StockDBContext stockDBContext = new StockDBContext();
        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);
            ViewModel viewModel = new ViewModel();
            StockBoxView stockboxview = new StockBoxView(viewModel);
            AddStockView addstockview = new AddStockView(viewModel);
            
            // Methode zum Hinzufügen von Stock-Objekten in die Datenbank
            stockDBContext.Stocks.Add(new Stock { Wkn = "123456", Titel = "Porsche Automobil Holding" });
            stockDBContext.Stocks.Add(new Stock { Wkn = "654321", Titel = "PayPal" });
            stockDBContext.SaveChanges();


        }
        protected override void OnExit(ExitEventArgs e)
        {
            base.OnExit(e);
        }
}   
       
}

View 1 AddStockView.cs

namespace analyser
{
    public partial class AddStockView : UserControl
    {
   
        public AddStockView(ViewModel vm)
        {
            InitializeComponent();
            this.DataContext = vm;
        }        
    }  
}

<UserControl x:Class="analyser.AddStockView"
             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:analyser">
    <Grid>
        <StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" Margin="10">
            <!-- Überschrift "Add new Stocks" -->
            <TextBlock Text="Add new Stocks" FontSize="20" FontWeight="Bold" Margin="0,0,0,20" />

            <!-- TextBox für den Namen des Wertpapiers -->
            <StackPanel Orientation="Horizontal" VerticalAlignment="Center">
                <Label Content="Name des Wertpapiers:" FontSize="16" Width="200" />
                <TextBox x:Name="TitelTextBox" Width="200" Margin="10,0" FontSize="16" Text="{Binding Titel, UpdateSourceTrigger=PropertyChanged}" />
            </StackPanel>

            <!-- TextBox für die WKN des Wertpapiers -->
            <StackPanel Orientation="Horizontal" VerticalAlignment="Center">
                <Label Content="WKN des Wertpapiers:" FontSize="16" Width="200" />
                <TextBox x:Name="WknTextBox" Width="200" Margin="10,0" FontSize="16" Text="{Binding Wkn, UpdateSourceTrigger=PropertyChanged}"/>
            </StackPanel>

            <!-- Absendebutton (linksbündig) -->
            <Button Width="150" Content="Absenden" HorizontalAlignment="Left" Command="{Binding SaveCommand}" />
        </StackPanel>
    </Grid>
</UserControl>

View 2 StockBox

namespace analyser
{
    public partial class StockBoxView : UserControl
    {
        public StockBoxView(ViewModel vm)
        {
            InitializeComponent();
            this.DataContext = vm;
        }     
    }

    
}

<UserControl 
        x:Class="analyser.StockBoxView"
         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:analyser">
        <Grid>
             <StackPanel>
            <!-- Verwenden Sie das Binding auf die ObservableCollection<Stock> ObservableStockList -->
            <ComboBox x:Name="StockComboBox" Width="150" SelectedIndex="0"
                  ItemsSource="{Binding ObservableStockList}" 
                  DisplayMemberPath="Titel" />
        </StackPanel>
        </Grid>
      
</UserControl>

ViewModel.cs

namespace analyser
{
    public class ViewModel : INotifyPropertyChanged
    {
        private Stock stock = new Stock();
        public ObservableCollection<Stock> ObservableStockList = new ObservableCollection<Stock>();

        private ListCollectionView ComboBoxItems;

        public ViewModel()
        {
            // Initialisierung der ObservableStockList und ComboBoxItems
            Init();
        }

        // Property für den Namen des Wertpapiers
        private string _titel;
        public string Titel
        {
            get => _titel;
            set
            {
                _titel = value;
                OnPropertyChanged(nameof(Titel));
            }
        }

        // Property für die WKN des Wertpapiers
        private string _wkn;
        public string Wkn
        {
            get => _wkn;
            set
            {
                _wkn = value;
                OnPropertyChanged(nameof(Wkn));
            }
        }

        private ICommand _saveCommand;
        
        public ICommand SaveCommand
        {
            get
            {
                if (_saveCommand == null)
                    _saveCommand = new RelayCommand(Save);
                return _saveCommand;
            }
        }

        public void Init(){

            foreach (var stock in App.stockDBContext.Stocks)
            {
                    ObservableStockList.Add(stock);
            }

             // Erstellen und Konfigurieren Sie den ListCollectionView
            ComboBoxItems = new ListCollectionView(ObservableStockList);
            ComboBoxItems.SortDescriptions.Add(new SortDescription("Titel", ListSortDirection.Ascending));
            ComboBoxItems.IsLiveSorting = true;


        }

        public void Save()
        {
            // Speichern Sie die Werte der Textboxen in das Stock-Objekt
            stock.Titel = Titel;
            stock.Wkn = Wkn;
            Console.WriteLine($"Generate new Stock with Titel {stock.Titel} and WKN {stock.Wkn}");
            App.stockDBContext.Stocks.Add(stock);
            
            // Hier können Sie die Logik zum Speichern des Stock-Objekts in die Datenbank oder an einen anderen Speicherort einfügen.
        }

        // ... (weiterer Code wie die Implementierung von INotifyPropertyChanged)
        
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

MainWindow.xaml

<Window x:Class="analyser.View"
        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:analyser"
        xmlns:uc="clr-namespace:analyser"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <!-- Define a style for TabItem -->
        <Style TargetType="TabItem">
            <Setter Property="FontSize" Value="16" /> <!-- Set the desired font size (e.g., 16) -->
        </Style>
    </Window.Resources>
    <Grid>
        <DockPanel>
            <Menu DockPanel.Dock="Top">
                <MenuItem Header="_File">
                    <MenuItem Header="_New"/>
                    <MenuItem Header="_Open"/>
                    <Separator />
                    <MenuItem Header="_Exit" />
                </MenuItem>
                <MenuItem Header="_Help">
                    <MenuItem Header="_About"/>
                </MenuItem>
            </Menu>

            <TabControl>
                <TabItem Header="add Stock">
                    <!-- Content for Tab 1 -->
                    <uc:AddStockView/>
                </TabItem>
                <TabItem Header="auswählen">
                    <!-- Content for Tab 1 -->
                    <uc:StockBoxView/>
                </TabItem>
                <TabItem Header="Tab 2">
                    <!-- Content for Tab 2 -->
                    
                </TabItem>
                <TabItem Header="Tab 3">
                    <!-- Content for Tab 3 -->
                    <TextBlock Text="This is Tab 3 content."/>
                </TabItem>
            </TabControl>
        </DockPanel>
    </Grid>
</Window>

View.cs

namespace analyser
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class View : Window
    {
        
        public View()
        {

            InitializeBindings();
        }
        
        private void InitializeBindings() {
            DataContext = this;
        }
    }
} 

Edit

MyProjekt
|___Views
|    |___AddStock
|    |    |___AddStock.xaml (UserControl)
|    |    |___AddStock.xaml.cs
|    |___StockBox
|         |___StockBox.xaml (UserControl)
|         |___StockBox.xaml.cs   
|
|___ViewModels
|   |___ViewModel.cs
|
|___Models
|   |___Stock.cs
|   |___StockBoxDBContext
|
|___App.xaml
|___App.xaml.cs
|___MainWindow.xaml
|___MainWindow.xaml.cs
Z.J
  • 301
  • 1
  • 7
  • You must not share a single static DbContext instance across your application. It's absolutely pointless to instantiate the UserControls in App.xaml.cs without ever adding them to the visual tree. Right now, the moment you leave the scope of the OnStartup method, the instances are destroyed. The DataContext of views is usually inherited from the parent or the parents context. This means in View.xaml.cs instead of `DataContext = this` you must write `DataContext = new ViewModel()`. –  Jul 24 '23 at 22:33
  • You usually want to avoid building the layout in C#. Constructing view elements in C# means you would have to insert the elements into the correct tree position explicitly. Using XAML makes this much easier. DbContext (database) handling belongs to the Model and not View Model. Because you directly bind to ObservableStockList you must use `CollectionViewSource.GetDefaultView(ObservableStockList )` to obtain the same default collection view that is used by the ComboBox. Otherwise the sorting and filtering won't have any effect. Or bind to the ComboBoxItems CollectionView directly. –  Jul 24 '23 at 22:33
  • Instead of overriding the OnStartup try to handle the Startup event. Maybe show the App.xaml file too so taht we can know how you configured the application to show the startup window. –  Jul 24 '23 at 22:33
  • A UserControl, as any other control, should never set its own DataContext. It should only have a parameterless constructor that calls InitializeComponent, but does not set the DataContext. The value of the DataContext property is inherited from the parent element of the UserControl when it is instantiated, e.g. when a DataTemplate that declares the UserControl is applied to a ContentControl or ContentPresenter. – Clemens Jul 25 '23 at 06:42
  • @Clemens i have seen this principle (you have added in the comment), but i was never sure what is the cause of this principle. Why is it good or bad? What are the disadvantages of making the usercontrol's code behind to its DataContext? How would you bind dependency properties of the usercontrol to its xaml? Please guide me to the right direction on this, either here or in private chat. Thanks – bazsisz Jul 25 '23 at 07:20
  • 1
    https://stackoverflow.com/a/40184402/1136211 – Clemens Jul 25 '23 at 07:27
  • 1
    https://stackoverflow.com/a/28982771/1136211 – Clemens Jul 25 '23 at 07:28
  • Thank you for the useful comments. Unfortunately, my question is not answered. I still don't know how to bind multiple UserControls to a ViewModel. @Clemens I am not an expert and therefore I can only orient myself on the examples from the internet. In these the following is often done: ` ```public partial class MyView : UserControl { public MyView() { InitializeComponent(); this.DataContext = this; } }``` – Z.J Jul 25 '23 at 08:47
  • @BionicCode If I write DataContext = new ViewModel() and do that in each View.xaml.cs as well, then each View.xaml will have its own instance of the ViewModel class. Can't that lead to possibly causing inconsistency? – Z.J Jul 25 '23 at 08:57
  • 1
    Not sure why you still have `this.DataContext = this;`. It should have become clear that you must not assign the DataContext at all. Sample code on the internet that shows this is just wrong. Those people do not know what they are talking about. – Clemens Jul 25 '23 at 09:53
  • 1
    You do also write something like `DataContext = new ViewModel();` only in the root element of a view, i.e. a Window or a Page. Never in a UserControl. – Clemens Jul 25 '23 at 09:56
  • 1
    I have posted an answer to show you the basic principle. Because it looks like you want to construct the application manually from App.xaml.cs I based my example on this scenario. Of course you can replace constructor dependencies with explicit instance creation in the related constructors . The most crucial point is to make use of DataContext inheritance so that you can reuse your custom controls. The internals of custom controls must never know anything about any view model. If your custom control depends on external data you must introduce dependency properties to enable data binding. –  Jul 25 '23 at 10:12
  • Another crucial point is to not create custom controls in C# code. this is 99.9% of all times avoidable and should be avoided in favor of maintainable and readable code and to simplify your layout design (this is not WinForms). –  Jul 25 '23 at 10:16
  • If you can't use inheritance, for example you have want to show dialogs, you must store a reference to the relevant view model (in case it must shared). Although a dialog usually has its dedicated view model (because it has its own GUI data context). The point is if you need to share instances you must store a reference to the particular instance. For example, if you need to share `ViewModel` then only call `new ViewModel()` once and store the instance reference to pass it around where needed. But this scenario is a special case. usually DataContext inheritance is all you need. –  Jul 25 '23 at 10:21
  • @Clemens Thank you for pointing this out. Please note my addition in the initial post. There you can see my folder structure. Can I conclude from your comment that in MainWindow.xaml.cs the statement `DataContext=new ViewModel()` is written and in AddStock.xaml.cs and StockBox.xaml.cs it is not written in? – Z.J Jul 25 '23 at 10:33
  • 1
    That should be clear now, yes. – Clemens Jul 25 '23 at 10:36
  • Conversely, there is only one ViewModel for the entire application and not a separate one for each UserControl? – Z.J Jul 25 '23 at 10:37
  • Thank you. I saw at @BionicCode that other ViewModels are also possible. – Z.J Jul 25 '23 at 10:59

1 Answers1

1

You must not explicitly assign the DataContext of a control. You should strive to enable the XAML parser to create the instances for you so that you can use markup language to design the layout conveniently.

Unless you explicitly assign a DataContext to an element the DataContext of the root element will be inherited to all children within the visual tree. This means you usually only assign the DataContext of the XAML root element.
You can use composition to design your view model classes to introduce a context specific DataContext to your layout (see example below).

The following example shows how to run the main window manually to allow to pass a view model to the constructor. It also shows how to use composition to introduce context related view model classes.

App.xaml

<!-- Remove the StartupUri attribute 
     and only assign a Startup event handler -->
<Application Startup="OnApplicationStarted">
  ...
</Application>

App.xaml.cs

partial class App : Application
{
  private void OnApplicationStarted(object sender, EventArgs e)
  {
    var someViewModel = new SomeViewModel();
    var mainViewModel = new MainViewModel(someViewModel);
    
    var mainWindow = new MainWindow(mainViewModel);

    // Start the GUI manually
    mainWindow.Show();
  }
}

MainWindow.xaml

<Window>
  <!-- All children of MainWindow inherit MainViewModel as DataContext -->
  <StackPanel>
    
    <!-- All children use the inherited MainViewModel as DataContext -->
    <ListBox />
    <TextBlock Text="{Binding ExampleText}" />
  
    <!-- Create a new DataContext scope -->
    <StackPanel DataContext="{Binding SomeViewModel}>

      <!-- All children inherit SomeViewModel as their DataContext -->

      <ListBox />
      <DataGrid />
      <MyUserControl SomeItems="{Binding SomeItemsSource}" />
    </StackPanel>

    <!-- MainViewModel DataContext scope continues -->
  </StackPanel>
</Window>

MainWindow.xaml.cs

partial class MainWindow : Window
{
  public MainWindow(MainViewModel mainViewModel)
  {
    InitilaizeComponent();

    // This should be the only place in your application 
    // where you set the DataContext from a constructor
    this.DataContext = mainViewModel;
  }
}

MyUserControl.xaml.cs

partial class MyUserControl : UserControl
{
  // A depndency property to allow binding to the DataContext
  public static DependencyProperty SomeItems = DependencyObject.Register(...);

  // DataContext will be inherited from the visual parent.
  // This enables maximum flexibility and reusability.
  public MyUserControl()
  {
    InitilaizeComponent();
  }
}

MyUserControl.xaml.cs

<UserControl>

  <!-- To enable reusability don't bind directly to the DataContext.
       Instead introduce dependency properties that you can bind the view model to. 
       Bind internals only to dependency properties defined on the current UserControl -->
  <ListBox ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, 
                                 Path=SomeItems}" />
</UserControl>

MainViewModel.cs

class MainViewModel : INotifyPropertyChanged
{
  public SomeViewModel SomeViewModel { get; }
  public string ExampleText { get; }

  public MainViewModel(SomeViewModel someViewModel)
  {
    this.SomeViewModel = someviewModel;
    this.ExampleText = "This is from MainViewModel";
  }
}

SomeViewModel.cs

class SomeViewModel : INotifyPropertyChanged
{
  public ObservableCollection<object> SomeItemsSource { get; }

  public SomeViewModel()
  {
    this.SomeItemsSource = new ObservableCollection<object>();
  }
}