Сам учусь и борюсь с тем, что, вероятно, является основной концепцией. Поэтому любые дополнительные объяснения приветствуются ... У меня есть следующий код, который отлично работает с событиями Button_Click и Code Behind. Я не против использования небольшого кода позади, потому что он в основном просто манипулирует представлением. Однако я хочу получить данные / бизнес-логику из ViewModel. Я могу подтвердить (с помощью messagebox.show), что мои данные передаются правильно, однако пользовательский интерфейс не обновляется с использованием новых данных, как это происходит, когда данные поступают из события Button_Click. Некоторое время у меня были трудности с этим, и я продолжаю находить решения. Насколько я знаю, мой подход полностью неверен. Очень хотелось бы знать, как это сделать.
WPF
<StackPanel Grid.Row = "1" VerticalAlignment = "Bottom">
<TextBox x:Name = "NumberOfDays" HorizontalAlignment = "Left" Background = "GreenYellow" Foreground = "Black">
</TextBox>
<StackPanel Orientation = "Horizontal">
<Button Content = "change hello row" Click = "Button_Click" HorizontalAlignment = "Left" Background = "GreenYellow" Foreground = "Black">
</Button>
<Button Content = "TestCommand5" Margin = "0 0 0 0" Padding = "5" VerticalContentAlignment = "Center" HorizontalContentAlignment = "Center" FontSize = "16"
CommandParameter = "{Binding objActiveJobClass.JobID}"
Command = "{Binding TestCommand5}"
IsTabStop = "False" FocusVisualStyle = "{DynamicResource MyFocusVisual}"
Style = "{Binding Mode=OneWay, Source = {StaticResource NavigationButtonStyle}}">
</Button>
</StackPanel>
<Label Content = "Gant Chart Area" HorizontalAlignment = "Left"/>
<ScrollViewer Width = "1200" HorizontalAlignment = "Left" VerticalScrollBarVisibility = "Auto" HorizontalScrollBarVisibility = "Auto">
<Grid >
<Canvas x:Name = "GantChartCanvas" HorizontalAlignment = "Left" Background = "Yellow" Height = "405" />
</Grid>
</ScrollViewer>
</StackPanel>
BackCode:
public partial class GantChartUserControl : UserControl
{
public GantChartUserControl()
{
InitializeComponent();
}
public GantChartUserControl(int Duration)
{
InitializeComponent();
CreateTimeLine(Duration);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
int variable = 0;
if (NumberOfDays != null)
{
int.TryParse(NumberOfDays.Text, out variable);
if (variable > 0)
{
CreateTimeLine(variable);
}
else
{
MessageBox.Show($"\"{NumberOfDays.Text}\" is not an INTEGER greater than Zero.");
}
}
}
public void CreateTimeLine(int duration)
{
MessageBox.Show($"CreateTimeLine duration {duration}");
GantChartCanvas.Children.Clear();
double ControlWidth = 100d;
int Duration = duration;
for (int i = 0; i < Duration; i++)
{
Label u1 = new Label();
u1.HorizontalAlignment = HorizontalAlignment.Left;
u1.VerticalAlignment = VerticalAlignment.Bottom;
u1.HorizontalContentAlignment = HorizontalAlignment.Center;
u1.VerticalContentAlignment = VerticalAlignment.Center;
u1.Background = IsOdd(i) ? Brushes.Gray : Brushes.DarkGray;
u1.Height = 30;
u1.Width = ControlWidth;
u1.SetValue(Canvas.LeftProperty, (ControlWidth * i));
u1.SetValue(Canvas.BottomProperty, 0d);
u1.Content = string.Concat("LABEL ", i + 1);
GantChartCanvas.Width = Duration * ControlWidth;
GantChartCanvas.Children.Add(u1);
}
}
public static bool IsOdd(int value)
{
return value % 2 != 0;
}
}
ViewModel
private ICommand _TestCommand5;
public ICommand TestCommand5
{
get
{
if (_TestCommand5 == null)
{
_TestCommand5 = new RelayCommand<object>(ExecuteTestCommand5, CanExecuteTestCommand5);
}
return _TestCommand5;
}
}
public bool CanExecuteTestCommand5(object parameter)
{
return true;
}
public void ExecuteTestCommand5(object parameter)
{
Debug.WriteLine($"\nDataBaseHelperClass: {System.Reflection.MethodBase.GetCurrentMethod()}");
int testint = 44;
GantChartUserControl objGantChartUserControl = new GantChartUserControl();
objGantChartUserControl.CreateTimeLine(testint);
}
Итак ... после прочтения текста вы фактически используете термин "код, стоящий за", по какой причине вы назвали его "BackCode" в заголовке вопроса? - И ... ваш текст содержит много шума (не относящийся к делу), что, прочитав его в первый раз, я подумал: «В чем проблема?» Возможно, перефразируйте текст без «истории» о том, как возникла проблема, и просто скажите, в чем заключается ваша проблема.
Две кнопки пытаются добиться того же. Кнопка с «событием щелчка» работает с использованием кода позади, но кнопка, привязанная к ICommand в ViewModel (вызывая тот же метод в коде позади), не работает. Пользовательский интерфейс не обновляется при использовании ICommand / ViewModel. Поэтому я считаю, что либо неправильно вызываю метод из ViewModel, либо есть что-то особенное в обновлении пользовательского интерфейса, о котором я не знаю. (INotifyPropertyChanged не проблема)
@mjordan «не работает» бессмысленно. Вызывается ли метод execute? «INPC - не проблема» - не будьте так уверены в своих суждениях, когда вы находитесь на такой ранней стадии в изучении чего-либо. INPC, безусловно, является важной частью ответа.
ОК. Из моего исходного вопроса: «Я могу подтвердить (с помощью messagebox.show), что мои данные передаются правильно». Я все время использую INPC и не верю, что это имеет значение, но скажите, пожалуйста, если я ошибаюсь. Правильное значение печатается в окне сообщения, поэтому я не верю, что проблема в INPC. Самоуверенность - не моя проблема, но я хотел бы получить ввод, который действительно отвечает на мой исходный вопрос («UI не обновляется с использованием новых данных, как это происходит, когда данные поступают из события Button_Click»). Благодарю.
Я нашел способ выполнить метод ViewModel из скрытого кода и получить статическое int, которое используется в скрытом коде. Code Behind обновляет интерфейс так, как я ожидал (в отличие от использования ICommand для выполнения метода в ViewModel). Я (добавил wpf x: name = "RefreshGantChart") И добавил в метод Button_Click следующее: Button RefreshGantChart = sender as Button; RefreshGantChart.Command.Execute (параметр RefreshGantChart.CommandPar); Продолжительность = ActiveJobViewModel.StaticDuration; Точно противоположно тому, что я пытался сделать, и ломает MVVM, но он работает
@mjordan Статическое целое число вместо возвращаемого значения. Думаю, теперь я понимаю основную проблему. Удачи.





Вы можете использовать платформу MVVMLight, это упростит вашу привязку.
С помощью этого фреймворка вы можете просто создать свойство public RelayCommand YourButton{ get; set; }
И в конструкторе вашей ViewModel вам просто нужно добавить эту строку
YourButton = new RelayCommand(Method); // Method is the method to call when you click on the button
После этого вам просто нужно привязать кнопку к свойству.
Небольшой пример:
ViewModel:
public RelayCommand MyButton { get; set; }
public MainViewModel()
{
MyButton = new RelayCommand(Action);
}
public void Action(){
Console.Writeline("Button Pressed");
}
WPF
<Button Command = "{Binding MyButton}" Content = "Button"/>
Когда вы используете привязку, лучше минимизировать ваш код.
Вам необходимо устранить путаницу OP относительно того, как пользовательский элемент управления может отражать изменения в модели представления, вызванные командой. Я не думаю, что в его модели просмотра есть что-то, кроме команды, которая беспомощно манипулирует временным экземпляром пользовательского элемента управления.
Я считаю, что ты прав, Эд. Я вижу, где я могу создать временный экземпляр, но я не знаю, как правильно манипулировать исходным пользовательским элементом управления.
Он может отразить изменение в своем пользовательском элементе управления с помощью mainViewModel. Ему просто нужно привязать свой пользовательский элемент управления к mainViewModel и использовать RaisePropertyChange? Или, может быть, я не очень хорошо понимаю вопрос
@mjordan Это была бы хорошая возможность для вас изучить основы привязки данных с помощью INotifyPropertyChanged. Вам нужно всего лишь быть на шаг впереди ученика. Я многому научился, исследуя ответы здесь. (Упс, вы ОП, но действует тот же принцип).
На ваш взгляд (xaml) сначала установите виртуальную машину в качестве DataContext. Есть способы сделать это, но я покажу вам только один:
<UserControl x:Class = "MyApp.GantChartUserControl"
...
xmlns:local = "clr-namespace:MyApp"> <!--adjust the location(namespace.folder) of your VM as you like-->
<UserControl.DataContext>
<local:GantChartUserControlViewModel/> <!--this sets the GantChartUserControlVM as DataContext-->
</UserControl.DataContext>
...
Представление (UserControl) не должно беспокоить никаких манипуляций с данными, вместо этого оно просто показывает (привязывается) к свойству ViewModel, которое преднамеренно должно быть раскрыто. Я действительно предлагаю поместить весь этот код, который вы написали, в ViewModel. Итак, что вы хотите иметь в коде позади (xaml.cs), это:
public partial class GantChartUserControl : UserControl
{
public GantChartUserControl()
{
InitializeComponent();
}
}
ViewModel:
public class GantChartUserControlViewModel : INotifyPropertyChanged
{
// boiler-plate
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
// Property you want to expose:
public ObservableCollection<int> MyIntegers
{
get {return _myIntegers;}
set { _myIntegers = value; OnPropertyChanged("MyIntegers"); }
}
private ObservableCollection<int> _myIntegers;
// ICommand
public ICommand TestCommand5 { get; private set;}
// constructor
public GantChartUserControlViewModel()
{
MyInteger = new ObservableCollection<int>();
new RelayCommand<object>(ExecuteTestCommand5, CanExecuteTestCommand5);
}
public bool CanExecuteTestCommand5(object parameter)
{
return true;
}
public void ExecuteTestCommand5(object parameter)
{
Debug.WriteLine($"\nDataBaseHelperClass: {System.Reflection.MethodBase.GetCurrentMethod()}");
int testint = (int)parameter;
CreateTimeLine(testint);
}
private void CreateTimeLine(int duration)
{
MyIntegers.Clear();
for(int i=0;i<duration;i++)
{
MyIntegers.Add(i);
}
}
}
Теперь, когда у вас есть виртуальная машина, нам нужно снова привязать ее к View. Итак, вернемся к вашему xaml, вместо использования Canvas я рекомендую использовать ItemsControl (или ListView)
<ScrollViewer>
<ItemsControl ItemsSource = {Binding MyIntegers, Mode=OneWay}>
<!--we have the integer collection, but View has the responsibility to set the layout, etc. which I usually call it the datatemplate-->
<ItemsControl.ItemTemplate>
<DataTemplate>
<Label HorizontalAlignment = "Left"
VerticalAlignment = "Bottom"
HorizontalContentAlignment = "Center"
VerticalContentAlignment = "Center"
Height = "30"
Width = "100">
<Label.Background>
<Binding>
<!--Why empty binding? it just means we are binding to the single int from the ItemsSource-->
<Binding.Converter>
<!--What is this converter? We'll get there later-->
<converter: IsOddToColorConverter/>
</Binding.Converter>
</Binding>
</Label.Background>
</Label>
</DataTemplate>
</ItemsControl.ItemTemplate>
<!-- I see that you wanted to have the labels put in horizontal, so we need to change the itemspanel from the default (vertical)-->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation = "Horizontal"/>
<!-- you could use WrapPanel, in case if you want it to wrap, if it does not fit horizontally-->
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ScrollViewer>
Итак, приведенный выше преобразователь - это инструмент для преобразования связанного целого числа в цвет фона. Вам нужно будет создать конвертер другого класса:
/// <summary>This convert number to brush</summary>
public class IsOddToColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo language)
{
// value is the integer input, we don't care about parameter or language
// output should be type of Brush
if (value is int && targetType == typeof(Brush))
{
bool isOdd = ((int)value) % 2 != 0;
return isOdd ? Brushes.Gray : Brushes.DarkGray;
}
// if input (and output) is unknown
return Brushes.Transparent;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo language)
{
// We don't care about this
throw new NotSupportedException();
}
}
Итак, как нам использовать этот конвертер? Вернемся к вашему просмотру (xaml) снова. На уровне UserControl укажите путь
xmlns:converter = "clr-namespace:MyApp.Converters" <!--if your converter you defined earlier is inside Converters folder-->
<!--since calling it as converter: IsoddToColorConverter will create a new instance. It might be better to declare this as a static resource, so memory usage won't grow, even if you are using it multiple times. For how to do it, please look in the internet-->
Тогда, вуаля, это должно соответствовать шаблону MVVM, и ваш код станет намного чище и проще в обслуживании.
Обратите внимание, что это самая основная идея того, что вам нужно делать. В конце концов вы найдете несколько фреймворков (Prism, MVVMLight и т. д.), Которые помогут вам сократить количество кодов, которые вам нужно написать. И я действительно рекомендую вам начать изучать MVVM раньше, тем лучше. Кодирование WPF в старом стиле Forms (много кода позади) не будет использовать величие WPF.
На первый взгляд это выглядит неплохо, но Windows.DataContext и value as int не компилируются.
протестируйте свой код. Попробуй это. Пожалуйста. int - это тип значения. Double - это тип значения.
Если OP не может заставить работать свой код, как именно вы ему помогаете, давая ему код, который даже не компилируется? Помимо этой проблемы, как targetType == typeof(SolidColorBrush)Когда-либо будет правдой?
Спасибо за ваш ответ. Это вдохновило меня переосмыслить свой подход.
BackCode это вещь? - Знаю только о термине «код позади».