Я написал приложение WPF. Он имеет следующие представления:
Конечно, у каждого представления есть файл xaml. Оба представления должны быть связаны с классом ViewModel. Чтобы убедиться, что DataContext обоих представлений указывает на один и тот же объект ViewModel, я передал его в качестве параметра конструктора. Я компилирую приложение в коде Visual Studio. Компиляция работает, но окно не запускается. Наверное, мой подход неверен. Я хотел спросить вас, что мне нужно изменить, чтобы приложение работало правильно. 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);
}
}
}
Просмотр 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>
Просмотр 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;
}
}
}
Редактировать
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
Обычно вы хотите избежать построения макета на C#. Создание элементов представления в C# означает, что вам придется явно вставлять элементы в правильную позицию дерева. Использование XAML значительно упрощает эту задачу. Обработка DbContext (базы данных) принадлежит модели, а не модели представления. Поскольку вы напрямую привязываетесь к ObservableStockList, вы должны использовать CollectionViewSource.GetDefaultView(ObservableStockList ) для получения того же представления коллекции по умолчанию, которое используется ComboBox. В противном случае сортировка и фильтрация не будут иметь никакого эффекта. Или привяжите к ComboBoxItems CollectionView напрямую.
Вместо переопределения OnStartup попробуйте обработать событие Startup. Может быть, покажите также файл App.xaml, чтобы мы могли узнать, как вы настроили приложение для отображения окна запуска.
UserControl, как и любой другой элемент управления, никогда не должен устанавливать свой собственный DataContext. Он должен иметь только конструктор без параметров, который вызывает InitializeComponent, но не устанавливает DataContext. Значение свойства DataContext наследуется от родительского элемента UserControl при его создании, например. когда DataTemplate, объявляющий UserControl, применяется к ContentControl или ContentPresenter.
@Clemens Я видел этот принцип (вы добавили в комментарии), но я никогда не был уверен, в чем причина этого принципа. Почему это хорошо или плохо? Каковы недостатки создания кода пользовательского контроля в его DataContext? Как бы вы привязали свойства зависимостей пользовательского элемента управления к его xaml? Пожалуйста, направьте меня в правильном направлении по этому вопросу, либо здесь, либо в приватном чате. Спасибо
Спасибо за полезные комментарии. К сожалению, на мой вопрос нет ответа. Я до сих пор не знаю, как связать несколько пользовательских элементов управления с ViewModel. @Clemens Я не эксперт, поэтому могу ориентироваться только на примеры из интернета. В них часто делается следующее: ` public partial class MyView : UserControl { public MyView() { InitializeComponent(); this.DataContext = this; } }
@BionicCode Если я напишу DataContext = new ViewModel() и сделаю это также в каждом View.xaml.cs, то каждый View.xaml будет иметь свой собственный экземпляр класса ViewModel. Не может ли это привести к возможному несоответствию?
Не знаю, почему у вас все еще есть this.DataContext = this;. Должно было стать ясно, что вы вообще не должны назначать DataContext. Пример кода в Интернете, который показывает, что это просто неправильно. Эти люди не знают, о чем говорят.
Вы также пишете что-то вроде DataContext = new ViewModel(); только в корневом элементе представления, то есть в окне или странице. Никогда в UserControl.
Я опубликовал ответ, чтобы показать вам основной принцип. Поскольку похоже, что вы хотите создать приложение вручную из App.xaml.cs, я основывал свой пример на этом сценарии. Конечно, вы можете заменить зависимости конструктора явным созданием экземпляра в связанных конструкторах. Наиболее важным моментом является использование наследования DataContext, чтобы вы могли повторно использовать свои пользовательские элементы управления. Внутренности пользовательских элементов управления никогда не должны знать ничего о какой-либо модели представления. Если ваш пользовательский элемент управления зависит от внешних данных, вы должны ввести свойства зависимости, чтобы включить привязку данных.
Еще один важный момент — не создавать собственные элементы управления в коде C#. этого можно избежать в 99,9% случаев, и его следует избегать в пользу поддерживаемого и читаемого кода и для упрощения дизайна макета (это не WinForms).
Если вы не можете использовать наследование, например, вы хотите показать диалоги, вы должны сохранить ссылку на соответствующую модель представления (на случай, если она должна быть общей). Хотя диалоговое окно обычно имеет свою выделенную модель представления (поскольку у него есть собственный контекст данных графического интерфейса). Дело в том, что если вам нужно поделиться экземплярами, вы должны сохранить ссылку на конкретный экземпляр. Например, если вам нужно поделиться ViewModel, вызовите new ViewModel() только один раз и сохраните ссылку на экземпляр, чтобы передавать ее там, где это необходимо. Но этот сценарий — частный случай. обычно наследование DataContext — это все, что вам нужно.
@Clemens Спасибо, что указали на это. Обратите внимание на мое дополнение в начальном посте. Там вы можете увидеть мою структуру папок. Могу ли я сделать вывод из вашего комментария, что в MainWindow.xaml.cs написано выражение DataContext=new ViewModel(), а в AddStock.xaml.cs и StockBox.xaml.cs его нет?
Теперь должно быть ясно, да.
И наоборот, есть только одна ViewModel для всего приложения, а не отдельная для каждого UserControl?
Спасибо. Я видел в @BionicCode, что возможны и другие ViewModels.





Вы не должны явно назначать DataContext элемента управления. Вы должны стремиться к тому, чтобы синтаксический анализатор XAML создавал экземпляры для вас, чтобы вы могли использовать язык разметки для удобного проектирования макета.
Если вы явно не назначите DataContext элементу, DataContext корневого элемента будет унаследовано всеми дочерними элементами в визуальном дереве. Это означает, что обычно вы назначаете только DataContext корневого элемента XAML.
Вы можете использовать композицию для разработки классов модели представления, чтобы ввести контекст, специфичный для вашего макета (см. пример ниже).
В следующем примере показано, как запустить главное окно вручную, чтобы разрешить передачу модели представления в конструктор. Он также показывает, как использовать композицию для представления классов модели представления, связанных с контекстом.
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>();
}
}
Вы не должны совместно использовать один статический экземпляр DbContext в своем приложении. Совершенно бессмысленно создавать экземпляры UserControls в App.xaml.cs, даже не добавляя их в визуальное дерево. Прямо сейчас, когда вы покидаете область действия метода OnStartup, экземпляры уничтожаются. DataContext представлений обычно наследуется от родительского или родительского контекста. Это значит, что в View.xaml.cs вместо
DataContext = thisнужно писатьDataContext = new ViewModel().