I've merged two programs, and now there are two DataContexts in my code. One is DataContext = this; and the other is DataContext = _uiData;. I'd like to keep the former, because there are many bindings relying on this DataContext (not shown in my code, though). For the latter, I changed it to textBox.Text = _uiData.ToString();, but it doesn't show anything. How would you take care of this kind of situation?
Here is my code:
MainWindow.xaml.cs
using Both_ListBox_n_TextBox_Binding.ViewModel;
using System.Collections.ObjectModel;
using System.Windows;
namespace Both_ListBox_n_TextBox_Binding
{
public partial class MainWindow : Window
{
public ObservableCollection<GraphViewModel> RightListBoxItems { get; }
= new ObservableCollection<GraphViewModel>();
private UISimpleData _uiData = new UISimpleData();
public MainWindow()
{
InitializeComponent();
RightListBoxItems.Add(new GraphViewModel("T1"));
RightListBoxItems.Add(new GraphViewModel("T2"));
RightListBoxItems.Add(new GraphViewModel("T3"));
DataContext = _uiData; // How can I show this?
//textBox.Text = _uiData.ToString(); // This doesn't show anything
DataContext = this; // I'd like to KEEP this
//RightListBox.ItemsSource = RightListBoxItems; // Works, but not for this time
}
}
}
MainWindow.xaml
<Window x:Class="Both_ListBox_n_TextBox_Binding.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:Both_ListBox_n_TextBox_Binding"
mc:Ignorable="d"
Title="MainWindow" Height="253.5" Width="297.5">
<Grid>
<ListBox x:Name="RightListBox"
ItemsSource="{Binding RightListBoxItems}" DisplayMemberPath="Text"
SelectionMode="Extended" Margin="20,20,20,100"/>
<TextBox Margin="100,0,0,40" HorizontalAlignment="Left" VerticalAlignment="Bottom" Height="22" Width="100"
Text="{Binding DoubleField, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}">
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding (Validation.Errors)[0].ErrorContent,
RelativeSource={RelativeSource Self}}"/>
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
</Grid>
</Window>
ViewModel/UISimpleData.cs
using System;
using System.ComponentModel;
namespace Both_ListBox_n_TextBox_Binding.ViewModel
{
public class UISimpleData : INotifyPropertyChanged, IDataErrorInfo
{
private double _doubleField = 5.5;
public double DoubleField
{
get
{
return _doubleField;
}
set
{
if (_doubleField == value)
return;
_doubleField = value;
RaisePropertyChanged("DoubleField");
}
}
public string this[string propertyName]
{
get
{
string validationResult = null;
switch (propertyName)
{
case "DoubleField":
{
if (DoubleField < 0.0 || DoubleField > 10.0)
validationResult = "DoubleField is out of range";
break;
}
default:
throw new ApplicationException("Unknown Property being validated on UIData");
}
return validationResult;
}
}
public string Error { get { return "Not Implemented"; } }
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string property)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
}
}
ViewModel/GraphViewModel.cs
namespace Both_ListBox_n_TextBox_Binding.ViewModel
{
public class GraphViewModel
{
public string Text { get; }
public GraphViewModel(string text) => Text = text;
}
}
Thank you in advance.
Related
My program has two windows. The content of TextBox on MainWindow should change the content of a TextBlock on CalcWindow. However, the TextBlock doesn't change, even after the TextBox is changed.
I added "RaisePropertyChanged" to UISimpleData. So, the content of TextBox is correctly changed. But, it doesn't change the TextBlock on CalcWindow.
MainWindow.xaml
<Window x:Class="DoubleToTextBlockBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DataUpdate" Height="220.276" Width="400">
<Grid Height="190" Margin="0,0,-0.667,0" VerticalAlignment="Top">
<Label Content="Target Value" HorizontalAlignment="Right" Margin="0,0,112,142" VerticalAlignment="Bottom" Width="78"/>
<TextBox Margin="0,0,24,142" HorizontalAlignment="Right" VerticalAlignment="Bottom" Height="22" Width="60"
Text="{Binding DoubleField, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}">
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding (Validation.Errors)[0].ErrorContent,
RelativeSource={RelativeSource Self}}"/>
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<TextBlock Text="{Binding DoubleField}" HorizontalAlignment="Right" VerticalAlignment="Bottom"
Margin="0,0,25,115" Height="22" Width="60"/>
<Button Name="ConfirmButton" Content="Confirm" Margin="85,0,25.666,58" HorizontalAlignment="Right" VerticalAlignment="Bottom" Click="ConfirmButton_Click"/>
</Grid>
</Window>
MainWindow.xaml.cs
using DoubleToTextBlockBinding.ViewModels;
using DoubleToTextBlockBinding.Views;
using System.Windows;
namespace DoubleToTextBlockBinding
{
public partial class MainWindow : Window
{
private UISimpleData _uiData = new UISimpleData();
public MainWindow()
{
InitializeComponent();
DataContext = _uiData;
}
private void ConfirmButton_Click(object sender, RoutedEventArgs e)
{
new CalcWindow().Show();
}
}
}
App.xaml.cs
using System.Windows;
namespace DoubleToTextBlockBinding
{
public partial class App : Application
{
public App()
{
System.Windows.FrameworkCompatibilityPreferences
.KeepTextBoxDisplaySynchronizedWithTextProperty = false;
}
}
}
Views/CalcWindow.xaml
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewModels="clr-namespace:DoubleToTextBlockBinding.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="DoubleToTextBlockBinding.Views.CalcWindow"
Title="Bound Window" Width="400" Height="212">
<Grid>
<TextBlock Text="{Binding DoubleField}" x:Name="textBox" Width="104"
Margin="148,84,0,0" HorizontalAlignment="Left" VerticalAlignment="Top">
<TextBlock.DataContext>
<viewModels:UISimpleData/>
</TextBlock.DataContext>
</TextBlock>
</Grid>
</Window>
Views/CalcWindow.xaml.cs
using System.Windows;
using DoubleToTextBlockBinding.ViewModels;
namespace DoubleToTextBlockBinding.Views
{
public partial class CalcWindow : Window
{
private UISimpleData _uiData = new UISimpleData();
public CalcWindow()
{
InitializeComponent();
this.DataContext = _uiData;
}
}
}
ViewModels/UISimpleData.cs
using System;
using System.ComponentModel;
using System.Windows;
namespace DoubleToTextBlockBinding.ViewModels
{
public class UISimpleData : INotifyPropertyChanged, IDataErrorInfo
{
private double _doubleField = 2.0;
public double DoubleField
{
get
{
return _doubleField;
}
set
{
if (_doubleField == value)
return;
_doubleField = value;
RaisePropertyChanged("DoubleField");
}
}
public string this[string propertyName]
{
get
{
string validationResult = null;
switch (propertyName)
{
case "DoubleField":
{
if (DoubleField < 0 || DoubleField > 5)
validationResult = "DoubleField is out of range";
break;
}
default:
throw new ApplicationException("Unknown Property being validated on UIData");
}
return validationResult;
}
}
public string Error { get { return "Not Implemented"; } }
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string property)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
//MessageBox.Show("Changed to " + DoubleField);
}
}
}
Expected behavior:
Change the content of TextBox to "3".
(You will see the TextBlock on the same window changes to "3".)
Click on the "Confirm" button.
A new window appears. Check if the content of the TextBlock is "3".
(The actual result becomes "2" here.)
... This is the best I can do for now. Please help me. Thank you.
you are creating new instances of your viewmodel in both classes. If you change something in the viewmodel in MainWindow, you have to pass this model to the CalcWindow.
my guess is that you should write the constructor for CalcWindow like this:
public CalcWindow(UISimpleData yourViewModelFromMainWindow)
{
InitializeComponent();
this.DataContext = yourViewModelFromMainWindow;
}
and in the MainWindow, in method ConfirmButton_Click
private void ConfirmButton_Click(object sender, RoutedEventArgs e)
{
new CalcWindow(this.DataContext as UISimpleData).Show();
}
I hope this is helpful. If it's not, do not hesitate to ask.
I've added a ScrollViewer behavior that allows me to scroll to the top when a property is set to true based on Scroll the scrollviewer to top through viewmodel. I've found that this works perfectly the first time, but subsequent attempts do not fire because the TwoWay binding isn't setting my property back to false.
Here is my simple project showing my issue:
MainWindow.xaml
<Window x:Class="WpfApp1.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:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="100" Width="200">
<ScrollViewer local:ScrollViewerBehavior.ScrollToTop="{Binding Reset, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="200"/>
</Grid.RowDefinitions>
<Button VerticalAlignment="Bottom" Content="Top" Command="{Binding Scroll}"/>
</Grid>
</ScrollViewer>
</Window>
MainWindow.xaml.cs
using System.Windows;
namespace WpfApp1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private ViewModel _ViewModel;
public MainWindow()
{
InitializeComponent();
DataContext = _ViewModel = new ViewModel();
}
}
}
ViewModel.cs
using System;
using System.ComponentModel;
using System.Windows.Input;
namespace WpfApp1
{
public class RelayCommand : ICommand
{
private readonly Action<object> action;
public RelayCommand(Action<object> action)
{
this.action = action;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
action(parameter);
}
}
public class ViewModel : INotifyPropertyChanged
{
private bool _reset = false;
public ViewModel()
{
Scroll = new RelayCommand(o =>
{
Reset = true;
});
}
public event PropertyChangedEventHandler PropertyChanged;
public bool Reset
{
get { return _reset; }
set
{
bool changed = value != _reset;
_reset = value;
if (changed)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Reset)));
}
}
}
public RelayCommand Scroll { get; set; }
}
}
ScrollViewerBehavior.cs
using System.Windows;
using System.Windows.Controls;
namespace WpfApp1
{
public static class ScrollViewerBehavior
{
public static readonly DependencyProperty ScrollToTopProperty = DependencyProperty.RegisterAttached("ScrollToTop", typeof(bool), typeof(ScrollViewerBehavior), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, (o, e) =>
{
if (o is ScrollViewer sc)
{
if ((bool)e.NewValue)
{
sc.ScrollToTop();
SetScrollToTop((FrameworkElement)o, false); // this should set the property back to false
}
}
}));
public static bool GetScrollToTop(FrameworkElement o)
{
return (bool)o.GetValue(ScrollToTopProperty);
}
public static void SetScrollToTop(FrameworkElement o, bool value)
{
o.SetValue(ScrollToTopProperty, value);
}
}
}
I know that if I take out the changed check on the property, it works; however, that is not ideal for my situation. When I look at the element through WPF Inspector, I see that the property on the ScrollViewer is false as it should be, but my ViewModel property remains true.
I'm not good in English but i wrote this example. Look at this
[MainWindow.xaml.cs]
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
AnyClass c = new AnyClass();
private void h1_Click(object sender, RoutedEventArgs e)
{
Test = true;
}
private void h2_Click(object sender, RoutedEventArgs e)
{
Test = false;
}
public bool Test
{
get { return (bool)GetValue(TestProperty); }
set
{
SetValue(TestProperty, value);
c.Val = value;
}
}
public static readonly DependencyProperty TestProperty =
DependencyProperty.Register("Test", typeof(bool), typeof(MainWindow), new PropertyMetadata(false));
}
[AnyClass.cs]
class AnyClass
{
private bool val = false;
public bool Val
{
get
{
return val;
}
set
{
val = value;
}
}
}
[mainWindow.xaml]
<Button Click="h1_Click" Content="true">
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=hUserControl, Path=Test}" Value="True">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=hUserControl, Path=Test}" Value="False">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
<Button Click="h2_Click" Content="false">
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=hUserControl, Path=Test}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=hUserControl, Path=Test}" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
I have the following code in my test application.
XAML file:
<Window x:Class="TestWpfApplication.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:TestWpfApplication"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Grid>
<CheckBox Grid.Row="0" IsChecked="{Binding IsItemEnabled}" Margin="11,30,-10,129">IsItemEnabled</CheckBox>
<ComboBox Grid.Row="0" Margin="15,54,352,225" ItemsSource="{Binding Source={StaticResource dataFromEnum}}">
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="IsEnabled">
<Setter.Value>
<MultiBinding Converter="{StaticResource EnabledConverter}">
<Binding RelativeSource="{RelativeSource Self}" Path="Content" />
<Binding RelativeSource="{RelativeSource PreviousData}" Path="IsItemEnabled" />
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
</Grid>
</Window>
ViewModel
using System.ComponentModel;
using System.Runtime.CompilerServices;
using TestWpfApplication.Annotations;
namespace TestWpfApplication
{
public class MainWindowViewModel : INotifyPropertyChanged
{
private bool _isItemEnabled;
public event PropertyChangedEventHandler PropertyChanged;
public bool IsItemEnabled
{
get { return _isItemEnabled; }
set
{
_isItemEnabled = value;
OnPropertyChanged(nameof(IsItemEnabled));
}
}
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Converter
public class EnabledConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values[0].ToString() != "Time" || (bool)parameter != false;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
How to do the same functionality when the combo box will bind with enum like this one? It`s possible at all?
public enum OpTypes
{
Time,
Cost,
Fuel
}
In the my converter i'm getting {DependencyProperty.UnsetValue} instead true or false and in the second parameter value i'm getting the values of enum. Another issue is that converter fires only first time when i open the combobox.
Or how to do it correct? Any suggestions please. Thanks in advance.
Now it works) Thanks for all
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using TestWpfApplication.Properties;
namespace TestWpfApplication
{
public class MainWindowViewModel : INotifyPropertyChanged
{
private bool _isItemEnabled;
private IList<OptType> _optimizationTypes;
private OptimizeTypes _selectedIndex;
public event PropertyChangedEventHandler PropertyChanged;
public OptimizeTypes SelectedIndex
{
get { return _selectedIndex;}
set
{
_selectedIndex = value;
OnPropertyChanged(nameof(SelectedIndex));
}
}
public IList<OptType> OptimizationTypes
{
get { return _optimizationTypes; }
set
{
_optimizationTypes = value;
OnPropertyChanged(nameof(OptimizationTypes));
}
}
public bool IsItemEnabled
{
get { return _isItemEnabled; }
set
{
_isItemEnabled = value;
InitOptimizationTypes(value);
OnPropertyChanged(nameof(IsItemEnabled));
}
}
public MainWindowViewModel()
{
InitOptimizationTypes(false);
}
private void InitOptimizationTypes(bool isEnabled)
{
OptimizationTypes = new List<OptType>
{
new OptType
{
TypeName = "Time",
IsItemEnabled = isEnabled,
},
new OptType
{
TypeName = "Cost",
IsItemEnabled = true,
},
new OptType
{
TypeName = "Fuel",
IsItemEnabled = true,
}
};
}
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
using System.Collections;
namespace TestWpfApplication
{
public class OptType
{
public string TypeName { get; set; }
public bool IsItemEnabled { get; set; }
}
}
<Window x:Class="TestWpfApplication.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:TestWpfApplication"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Grid>
<CheckBox Grid.Row="0" IsChecked="{Binding IsItemEnabled}" Margin="11,30,15,129">IsItemEnabled</CheckBox>
<ComboBox Grid.Row="0" Margin="15,54,352,231" IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding OptimizationTypes}" DisplayMemberPath="TypeName"
SelectedIndex="{Binding SelectedIndex, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
>
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="IsEnabled" Value="{Binding IsItemEnabled}"/>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
</Grid>
</Window
>
sorry if you think this is a duplication of another post, but I tried every possible solution out there and I can't make it work.
The question is a two parter, but it's about the same code, so I thought I can ask the question in the same thread.
I try to do order system in C#, wpf, vs2015 using mvvm without any (hard coded) couplings. There is two things I need to do. First is to fire an event which I need to capture in the ViewModel, and second, when the number of articles is over a certain level, the background of the listboxitem for that article should be green (otherwise white/grey)
For this I use a ListBox and some listBoxItems.
MainWindow.xaml (the part that matters)
<Window x:Class="Sequence_Application_2.GUI.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:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:acb="clr-namespace:AttachedCommandBehavior;assembly=AttachedCommandBehavior"
mc:Ignorable="d"
....
<Window.DataContext>
<Grid Grid.Column="0" Margin="10">
<ListBox x:Name="LstViewFlows" SelectedItem="{Binding SelectedFlow.Name}" ItemsSource="{Binding Flows.Keys}" >
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}" >
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedFlow.CanDeliver, UpdateSourceTrigger=PropertyChanged}" Value="true" >
<Setter Property="ListBoxItem.Background" Value="DarkGreen" />
<Setter Property="FontWeight" Value="Bold"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<i:InvokeCommandAction Command="{Binding SetSelectedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
</Grid>
Question 1)
I need to bind a command to a "MouseLeftButtonDown" event when I click on a listboxItem in a listbox. I tried the ACB-solution but can't get it to work. At last I tried the code above and I can't get it to trigger when I click on an item, but below the items, in the empty part of the listbox (not on a listboxitem), it tiggers as it should. Guess I need to rearange my xaml-file or something, but I tried for two days now I nothing seems to work.
The command is named "setSelectedCommand" and is implemented like this
internal class SetSelectedFlowCommand : ICommand
{
private FlowsViewModel _flowsViewModel;
public SetSelectedFlowCommand(FlowsViewModel flowsViewModel)
{
_flowsViewModel = flowsViewModel;
}
/// <summary>
///
/// </summary>
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_flowsViewModel.SetSelectedFlow();
}
}
As I said, if I click on the ListBoxItem - nothing happends, but if i click in the ListBox - the command is fired (but nothing is "selected")
Question 2
As you see in the xaml above, I try to set the background color for a ListBoxitem, based on the value .CanDeliver in an object that is stored in a dictionary in the ViewModel. The variable Flow is the dictionary and SelectedFlow is supposed to be the flow that is selected.
This is an order system and CanDeliver is a variable that tells the operator/user if there is enough products to be delivered to the customer. If there are enough the listboxitem should be green, otherwise remain white/grey. Do you understand my question? Can I do it like this? Refering to an object inside a dictionary? (it's triggered by an INotifyPropertyChanged in the objects)
Hope you can help me because I have no more hair to pull from my head now ;-)
You don't need to use an event handler if all you want is to get the selectedItem. I have build an working example for you to show how to use binding only (MVVM) to achieve your requirements in your question:
C# (ViewModel):
using System;
using System.Collections.Generic;
using System.Windows;
using System.ComponentModel;
using System.Collections.ObjectModel;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
MyViewModel mvm = new MyViewModel()
{
Flows = new ObservableCollection<Flow>()
{
new Flow() { Name = "Flow1" },
new Flow() { Name = "Flow2" },
new Flow() { Name = "Flow3" , Amount=1},
new Flow() { Name = "Flow4" }
}
};
this.DataContext = mvm;
}
}
public class MyViewModel : ObservableObject
{
private Flow _selectedflow;
public ObservableCollection<Flow> Flows
{
get;
set;
}
public Flow SelectedFlow
{
get { return _selectedflow; }
set
{
if (value != _selectedflow)
{
_selectedflow = value;
RaisePropertyChanged("SelectedFlow");
}
}
}
}
public class Flow : ObservableObject
{
private string _name;
private int _amount;
public string Name
{
get { return _name; }
set
{
if (value != _name)
{
_name = value;
RaisePropertyChanged("Name");
}
}
}
public bool CanDeliver
{
get
{
return Amount > 0;
}
}
public int Amount
{
get { return _amount; }
set
{
if (value != _amount)
{
_amount = value;
RaisePropertyChanged("Amount");
RaisePropertyChanged("CanDeliver");
}
}
}
}
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, e);
}
}
protected void RaisePropertyChanged(String propertyName)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
}
}
XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="300" Width="350">
<Grid>
<StackPanel Orientation="Vertical">
<ListBox SelectedItem="{Binding SelectedFlow}" ItemsSource="{Binding Flows}" DisplayMemberPath="Name">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}" >
<Style.Triggers>
<DataTrigger Binding="{Binding CanDeliver, UpdateSourceTrigger=PropertyChanged}" Value="true" >
<Setter Property="Background" Value="DarkGreen" />
<Setter Property="FontWeight" Value="Bold"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
<TextBlock Text="{Binding SelectedFlow.Name}"/>
</StackPanel>
</Grid>
</Window>
Result: You can see the Flow item with CanDeliver = True (Amount>0) has a Green background. And the TextBlock is showing the SelectedFlow.
I started a new small project, using ModernUI for WPF.
I've to display a Dialog with only two textbox and two button(ok/cancel).
The two textbox have some validation(one is a name that should be >0, the other should be an email).
Currently I've the following:
var userEditionForm = new UserEditionForm(oneUserConfigurationViewModel);
var dlg = new ModernDialog
{
Title = "User edition",
Content = userEditionForm,
Width = 300
};
dlg.Buttons = new[] {dlg.OkButton, dlg.CancelButton};
dlg.ShowDialog();
return dlg.DialogResult.HasValue && dlg.DialogResult.Value;
The content user control is:
<UserControl x:Class="Test.UserControls.UserEditionForm"
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="300">
<Grid>
<StackPanel Orientation="Vertical">
<StackPanel.Resources>
<Style TargetType="StackPanel">
<Setter Property="Orientation" Value="Horizontal" />
<Setter Property="Margin" Value="0,0,0,4" />
</Style>
<Style TargetType="Label" BasedOn="{StaticResource {x:Type Label}}">
<Setter Property="Width" Value="100" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
</StackPanel.Resources>
<StackPanel>
<Label Content="Name" Target="{Binding ElementName=TextFirstName}" />
<TextBox x:Name="TextFirstName" Width="150"
Text="{Binding User.Name, RelativeSource={RelativeSource AncestorType=UserControl}, Mode=TwoWay, ValidatesOnDataErrors=True}" />
</StackPanel>
<StackPanel>
<Label Content="Email" Target="{Binding ElementName=TextEmail}" />
<TextBox x:Name="TextEmail" Width="150"
Text="{Binding User.Email, RelativeSource={RelativeSource AncestorType=UserControl}, Mode=TwoWay, ValidatesOnDataErrors=True}" />
</StackPanel>
</StackPanel>
</Grid>
</UserControl>
The validation is made, but I would like to prevent the user to click "OK" until the validation is good. Is it possible? If yes, how?
This was actually pretty tricky but it worked for me:
ViewModel
public class UserInfo : INotifyPropertyChanged, IDataErrorInfo
{
private static Regex emailRegex = new Regex(#"^\w+(?:\.\w+)*?#[a-zA-Z_]+?\.[a-zA-Z]{2,3}$");
private string firstname;
private string email;
public string FirstName
{
get { return firstname; }
set { firstname = value; OnPropertyChanged(); }
}
public string EMail
{
get { return email; }
set { email = value; OnPropertyChanged(); }
}
public string Error
{
get { return null; }
}
public string this[string columnName]
{
get
{
switch (columnName)
{
case "FirstName":
return string.IsNullOrWhiteSpace(FirstName) ? "Firstname is required!" : null;
case "EMail":
return EMail == null || !emailRegex.IsMatch(EMail) ? "The Email Address is not valid!" : null;
default:
return null;
}
}
}
public LoginCommand LoginCommand { get; private set; }
public UserInfo()
{
LoginCommand = new LoginCommand(this);
}
private void OnPropertyChanged([CallerMemberName]string propertyName = "")
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
}
Command (No clue what you want to call it...)
public class LoginCommand : ICommand
{
private UserInfo info;
private ICommand attachedCommand;
public bool CanExecute(object parameter)
{
return parameter != null && info["FirstName"] == null && info["EMail"] == null;
}
public event EventHandler CanExecuteChanged = delegate { };
public void Execute(object parameter)
{
Debug.WriteLine("This Works!");
//Add execution logic here
if (attachedCommand != null)
{
attachedCommand.Execute(parameter); //should close the window
}
}
private void Info_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
CanExecuteChanged(this, new EventArgs());
}
public LoginCommand(UserInfo info)
{
this.info = info;
info.PropertyChanged += Info_PropertyChanged;
}
public void AttachCommand(ICommand command) //attach the original command here
{
attachedCommand = command;
}
}
Usage with ModernUI
UserInfo info = new UserInfo();
UserInfoForm form = new UserInfoForm(info);
ModernDialog dialog = new ModernDialog
{
Title = "User edition",
Content = form,
Width = 300
};
Button btnOk = dialog.OkButton;
ICommand originalCommand = btnOk.Command; //the original command should close the window so i keep it
btnOk.Command = info.LoginCommand;
info.LoginCommand.AttachCommand(originalCommand); //and attach it to my command
dialog.Buttons = new[] { btnOk, dialog.CancelButton };
dialog.ShowDialog();