In this code:
<ListBox
x:Name="DataList1"
xmlns:local="clr-namespace:xaml_binding_commands"
>
<ListBox.Resources>
<local:CommandUp x:Key="CommandUp1"></local:CommandUp>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBox
Text="{Binding Height,Mode=TwoWay,StringFormat=00\{0:d\}}"
InputScope="Number"/>
<RepeatButton
Content="+"
Command="{StaticResource CommandUp1}"
CommandParameter="{Binding }"
/>
<TextBox
Text="{Binding Weight,Mode=TwoWay}"
InputScope="Number"/>
<RepeatButton
Content="+"
Command="{StaticResource CommandUp1}"
CommandParameter="{Binding Weight}"
/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And this
namespace xaml_binding_commands
{
public class CommandUp : ICommand
{
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
if (parameter is Entry)
{
Entry EntryToUp = (Entry)parameter;
EntryToUp.Height +=1; // no worky, which field to increment?!
}
if (parameter is Int32)
{
Int32 EntryFieldToUp = (Int32)parameter;
EntryFieldToUp += 1; // no worky
}
}
}
public class Entry : INotifyPropertyChanged
{
private Int32 _Height;
public Int32 Height
{
get { return _Height; }
set { _Height = value; PropChange("Height"); }
}
private Int32 _Weight;
public Int32 Weight
{
get { return _Weight; }
set { _Weight = value; PropChange("Weight"); }
}
private void PropChange(String PropName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(PropName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
public partial class MainPage : PhoneApplicationPage
{
public MainPage()
{
InitializeComponent();
this.Loaded += MainPage_Loaded;
}
private ObservableCollection<Entry> _People = new ObservableCollection<Entry>();
public ObservableCollection<Entry> People
{
get { return _People; }
set { _People = value; }
}
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
DataList1.ItemsSource = People;
People.Add( new Entry() { Height=67, Weight=118 } );
}
}
}
Can I pass the field that the textbox is bound to by reference? If I pass the entire class, an Entry, to the CommandParameter so it can operate and increment, I have no way of knowing which set of TextBoxes and Buttons caused the Command.Execute. If I pass the same thing that the TextBox is bound to, namely, individually: Weight,Height then the Command.Execute has no way of affecting the input. Usually I would PassByReference and my Int32 would be boxed, and my general operate function could operate on the by-ref parameter. Can I do this somehow in XAML?
If I was doing this, I would use MVVM Light and RelayCommand<string>. This means that you can pass in one (or more) parameters in as part of the binding to the ICommand.
This means that you could have multiple bindings to a single event handler attached to a button, and each binding could have a different parameter that let you know where it came from.
Update 1
MVVM Light is an MVVM library that is compatible with pretty much everything, from standard WPF to Windows 8.1 to Windows phone. See http://www.mvvmlight.net/. It is probably the most popular MVVM lib according to NuGet download stats, and the one that I tend to prefer.
For an example of how to use MVVM light with a CommandParameter, see the top voted answer at MVVM Light RelayCommand Parameters.
For an example of how to pass in two or more parameters to a RelayCommand, see How to Passing multiple parameters RelayCommand?
Update 2
Just looking at your code, I would use MVVM. I generally prefer MVVM to code behind (this is a whole discussion in itself). If you put all of your data in the ViewModel, and used bindings to let your XAML View update the ViewModel, I think things would become a lot easier to develop and maintain.
Related
Edit:
Ok after finally playing around numerous times without no luck, I have created a very small Wpf application. You can directly copy this code. Notice when you change values in the TextBox and press the Test button, the values never get updated. I don't understand why the two way binding dosen't work. Please help.
Here is the xaml:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListView Grid.Row="0"
ItemsSource="{Binding Path=Demo.CurrentParameterValue,Mode=TwoWay}"
HorizontalAlignment="Center" VerticalAlignment="Center">
<ListView.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=.,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Width="100"></TextBox>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Grid.Row="1" Click="Button_Click">TEST</Button>
</Grid>
Here is the xaml.cs:
namespace WpfApp9
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window, INotifyPropertyChanged
{
private VmServiceMethodsViewDataGridModel _demo;
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
public VmServiceMethodsViewDataGridModel Demo
{
get => _demo;
set
{
_demo = value;
OnPropertyChanged("Demo");
}
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
Demo = new VmServiceMethodsViewDataGridModel();
Demo.CurrentParameterValue.Add(1);
Demo.CurrentParameterValue.Add(2);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var collection = Demo.CurrentParameterValue;
MessageBox.Show(string.Format("Values are {0}, {1}", collection[0], collection[1]));
}
}
public class VmServiceMethodsViewDataGridModel : INotifyPropertyChanged
{
private List<object> _currentParameterValue;
public List<object> CurrentParameterValue
{
get => _currentParameterValue;
set
{
_currentParameterValue = value;
OnPropertyChanged("CurrentParameterValue");
}
}
public VmServiceMethodsViewDataGridModel()
{
CurrentParameterValue = new List<object>();
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
The problem with your binding is that you are trying to bind to an object. This is perfectly fine in a OneWay/OneTime scenario. But not when using binding TwoWay. You can change the value of a property e.g. in your view model, but you can't change the object instance itself. In your specific case, the binding would have to send the new long input to the view model's value collection and replace the old value. Of course this will never happen as Binding is not designed to work this way.
The technical reason is that changing the instance would mean to change the Binding.Source. Once the binding is active (controlled by a BindingExpression) it becomes immutable. Changing the source is not allowed. That's also the reason why {Binding Source={DynamicResource ...}} won't work. The BindingSource can only be static (or StaticResource - not changing resource).
You usually bind to properties. In a TwoWay binding scenario Binding can simply update the property's value. So the solution to your problem is to wrap the long values into a class and bind the TextBox to a property of this class to retrieve/modify the actual value.
In this context your code looks too complicated.
Your object structure is too complex or unnatural.
You don't need to apply the DataTemplate to a ContentControl (in XAML).
And of course as this is a UWP application, use x:Bind where possible as it will improve performance. The converter is redundant as Binding and x:Bind allow a nested PropertyPath e.g.
<ListView ItemsSource="{Binding CurrentParameterValue.ListParameterValues}">
ItemsControl.ItemsSource doesn't need a TwoWay binding. The ItemsControl will never update/replace the source collection. If you don plan to replace the source collection in the view model (e.g., AtlasMethodParameterList = new ObservableCollection<>()), then you can even set the binding mode to OneTime (which would be the default for x:Bind).
I recommend to use OneTime and if you need to replace the collection, rather call Clear() on the collection and add the new items. This will improve the performance.
Never use async void in a method signature except for event handlers.
Always use async Task, when the return type is void or when returning a value async Task<TResult>. Otherwise you will experience unexpected side effects, especially when encountering exceptions:
// An async void method must return Task
private async Task GetParameterList(string obj)
Also async methods should always be awaited. This means the method calling and awaiting an async method must itself return Task or Task<T> to be awaitable. A method returning type void cannot be awaited.
All DependencyProperty of every control, have their Binding.UpdateSourceTrigger set to UpdateSourceTrigger.PropertyChanged by default.
Exceptions are properties that are likely to raise too much consecutive property changes like a TextBox would do on each input/key press. TextBox.Text has the default set to UpdateSourceTrigger.LostFocus.
You should remove all redundant UpdateSourceTrigger.PropertyChanged from the bindings to improve readability.
Consider to use out instead of ref if you don't intend to read the variable. If you only set the value prefer to use out to hint your intent to any reader. Use in if don't intent to modify the reference (read-only reference).
Your Set method should look something like this:
protected virtual void Set<TValue>(out TValue valueTarget, TValue value, [CallerMemberName] string propertyName = null)
{
if (value != valueTarget)
{
valueTarget = value;
OnPropertyChanged(propertyName);
}
}
I refactored your complete code trying to improve it:
Parameter.cs
// The type that wraps the actual parameter value.
// Consider to use dedicated types e.g., LongParameter instead, to allow a strongly typed Value property instead of a basic property of type object.
// This prevents implicit boxing/unboxing in order to convert from object/reference type to primitive/value type and vice versa. This will improve performance.
// (Only needed because we are dealing with primitive/value types like long, double, etc)
// You would then have to define a DataTemplate for each type. Don't forget to set x:DataType on each DataTemplate.
public class Parameter : BindableBase
{
protected Parameter(object value)
{
this.Value = value;
}
private object value;
public object Value
{
get => this.value;
set => Set(out this.value, value);
}
}
VmServiceModel.cs
public class VmServiceModel : BindableBase
{
public VmServiceModel()
{
this.Parameters = new List<Parameter>();
}
private List<Parameter> _parameters;
public List<Parameter> Parameters
{
get => this._parameters;
set => Set(out this._parameters, value);
}
}
ViewModel.cs
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
this.AtlasMethodParameterList = new ObservableCollection<VmServiceModel>();
}
private ObservableCollection<VmServiceModel> _atlasMethodParameterList;
public ObservableCollection<VmServiceModel> AtlasMethodParameterList
{
get => _atlasMethodParameterList;
set => Set(out _atlasMethodParameterList, value);
}
private async Task GetParameterList(string obj)
{
foreach (var item in this.ParametersCollection)
{
var vmServiceModel = new VmServiceModel();
vmServiceModel.Parameters
.AddRange(item.Value.Cast<long>().Select(innerItem => new Parameter(innerItem)));
this.AtlasMethodParameterList.Add(vmServiceModel);
}
}
}
MainPage.xaml.cs
public sealed partial class MainPage : Page
{
public ViewModel ViewModel { get; set; }
public MainPage()
{
this.InitializeComponent();
this.ViewModel = new ViewModel();
}
}
MainPage.xaml
<Page>
<Page.Resources>
<DataTemplate x:Key="ListIntTemplate" x:DataType="local:VmServiceModel">
<ListView ItemsSource="{x:Bind Parameters}"
HorizontalAlignment="Center"
SelectionMode="None" Background="Transparent">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<controls:WrapPanel VerticalAlignment="Top"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:Parameter">
<TextBox Text="{Binding Value Mode=TwoWay}" Height="36" Width="65"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</DataTemplate>
</Page.Resources>
<Grid>
<ListView ItemsSource="{x:Bind ViewModel.AtlasMethodParameterList}"
ItemTemplate="{StaticResource ListIntTemplate}">
</ListView>
</Grid>
</Page>
But when I change the values in the TextBox it dosen't update back the source that is the CurrentParameterValue property.
Binding in ListView doesn't know how to update the Property of type object because it's ItemsSource and it can update only ICollection such as you can't interact with object like List in C#. for example:
object MyList = new object();
MyList.Add("something"); // Compile error
And in my viewmodel the object which can be a list of long, list of double etc comes from an external API.
You need this solution then.
public class VmServiceMethodsViewDataGridModel : BindableBaseThreadSafe
{
private List<object> _currentParameterValue; // or ObservableCollection
public List<object> CurrentParameterValue
{
get => _currentParameterValue;
set => Set(ref _currentParameterValue, value);
}
}
Additionally
I have no idea what do you want to achieve or solve with this syntax
<ListView ItemsSource="{x:Bind ViewModel.AtlasMethodParameterList,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}">
Everything must work with this
<ListView ItemsSource="{Binding AtlasMethodParameterList}">
Mode=TwoWay is default Mode, you may not include it here explicitly.
UpdateSourceTrigger=PropertyChanged (Default is LostFocus) is needed in UI->VM direction, not in a back way. So, it's useless here. You may apply it to the TextBox in template instead.
EDIT
Because Two-way Binding requires explicit Path and the target must be a Property which contains Setter.
The workaround with your Demo app
<ListView Grid.Row="0"
ItemsSource="{Binding Demo.CurrentParameterValue}"
HorizontalAlignment="Center" VerticalAlignment="Center">
<ListView.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Value, UpdateSourceTrigger=PropertyChanged}" Width="100"></TextBox>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
public partial class MainWindow : Window, INotifyPropertyChanged
{
private VmServiceMethodsViewDataGridModel _demo;
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
public VmServiceMethodsViewDataGridModel Demo
{
get => _demo;
set
{
_demo = value;
OnPropertyChanged("Demo");
}
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
Demo = new VmServiceMethodsViewDataGridModel();
Demo.CurrentParameterValue.Add(new MyItem { Value = 1 });
Demo.CurrentParameterValue.Add(new MyItem { Value = 2 });
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var collection = Demo.CurrentParameterValue;
MessageBox.Show(string.Format("Values are {0}, {1}", collection[0].Value, collection[1].Value));
}
}
// here it is
public class MyItem
{
public object Value { get; set; }
}
public class VmServiceMethodsViewDataGridModel : INotifyPropertyChanged
{
private List<MyItem> _currentParameterValue;
public List<MyItem> CurrentParameterValue
{
get => _currentParameterValue;
set
{
_currentParameterValue = value;
OnPropertyChanged("CurrentParameterValue");
}
}
public VmServiceMethodsViewDataGridModel()
{
CurrentParameterValue = new List<MyItem>();
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
Additionally you may implement INPC for the Value regarding to your needs.
I'm learning WPF.
In one of the exercises, I have a TextBox and buttons Cut and Paste. The following is enough to implement Cut and Paste functionality:
XAML:
<DockPanel>
<WrapPanel DockPanel.Dock="Top" Margin="3">
<Button Command="ApplicationCommands.Cut"
CommandTarget="{Binding ElementName=txtEditor}"
Width="60">
_Cut
</Button>
<Button Command="ApplicationCommands.Paste"
CommandTarget="{Binding ElementName=txtEditor}"
Width="60" Margin="3,0">
_Paste<
/Button>
</WrapPanel>
<TextBox AcceptsReturn="True" Name="txtEditor" />
</DockPanel>
When pressed, the button Cut executes the ApplicationCommands.Cut on the TextBox with name txtEditor. When needed, the button will ask the TextBox with name textEditor if it can execute a Cut command, and when pressed it will order the textEditor to execute the Cut command.
Fairly straightforward. It works fine.
Just for Fun, I'd like to implement another button: Clear. When pressed it should clear the TextBox. The Textbox class has a method Clear.
<Button Command="ApplicationCommands.Clear"
CommandTarget="{Binding ElementName=txtEditor}"
Width="60">
Clear
</Button>
Alas, this won't work. ApplicationCommands doesn't have a Clear. Should I implement a custom command, as suggested in this example?
I tried the following:
I implemented CanExecute and Executed methods in my window:
public partial class CustomCommandSample : Window
{
public CustomCommandSample()
{
InitializeComponent();
}
private void ClearCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
private void ClearCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
txtEditor.Clear();
}
}
A static CustomCommands class:
public static class CustomCommands
{
public static RoutedUICommand Clear => new RoutedUICommand (
"Clear",
"Clear",
typeof(CustomCommands));
}
Finally the XAML:
(Note: the classes in this project are in namespace WpfCommandDemo. Xaml refers to it as Local)
<Window x:Class="WpfTutorialSamples.Commands.UsingCommandsSample"
xmlns="...
xmlns:local="clr-namespace:WpfCommandDemo"
Title="UsingCommandsSample" Height="100" Width="200">
<Window.CommandBindings>
<CommandBinding Command="CustomCommands.Clear"
CanExecute="ClearCommand_CanExecute"
Executed="ClearCommand_Executed" />
</Window.CommandBindings>
<DockPanel>
<WrapPanel DockPanel.Dock="Top" Margin="3">
<Button Command="CustomCommands.Clear"
CommandTarget="{Binding ElementName=txtEditor}"
Width="60">
Clear
</Button>
... (other buttons: cut / paste, as above
</WrapPanel>
<TextBox AcceptsReturn="True" Name="txtEditor" />
</DockPanel>
Although this compiles, The constructor of CustomCommandSample throws an XamlParseException:
Type reference cannot find type named
'{http://schemas.microsoft.com/winfx/2006/xaml/presentation}CustomCommands'.
Should I solve the problem using Custom Commands? What should I change? Or am I completely wrong, and should I solve this differently
To use CustomCommands in XAML, you'll need to add a reference to it. In the element, add a line:
xmlns:custom="clr-namespace:MyApplication.NamespaceWithCustomInIt"
Replacing the namespace value as appropriate. Then you should be able to reference CustomCommands anywhere in XAML as custom:CustomCommands (may have to bind, I'll check later).
Should I solve the problem using Custom Commands?
Yes. This is how to solve this using the Model-View-ViewModel (MVVM) design pattern which is the recommended design pattern to use when developing XAML based UI applications.
From this blog post:
WPF provides two implementations of the ICommand interface; the System.Windows.Input.RoutedCommand and System.Windows.Input.RoutedUICommand where the latter is a subclass of the former that simply adds a Text property that describes the command. However, neither of these implementations are especially suited to be used in a view model as they search the visual tree from the focused element and up for an element that has a matching System.Windows.Input.CommandBinding object in its CommandBindings collection and then executes the Execute delegate for this particular CommandBinding. Since the command logic should reside in the view model, you don’t want to setup a CommandBinding in the view in order to connect the command to a visual element. Instead, you can create your own command by creating a class that implements the ICommand. The below implementation is a common one that invokes delegates for the Execute and CanExecute methods:
public class DelegateCommand: System.Windows.Input.ICommand
{
private readonly Predicate<object> _canExecute;
private readonly Action<object> _execute;
public DelegateCommand(Action<object> execute)
: this(execute, null) { }
public DelegateCommand(Action<object> execute, Predicate<object> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter) => _canExecute == null ? true : _canExecute(parameter);
public void Execute(object parameter) => _execute(parameter);
public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
Once you have an implementation of the ICommand interface, it's easy to use in your view models:
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
ClearCommand = new DelegateCommand(Clear);
}
private string _text;
public string Text
{
get { return _text; }
set { _text = value; NotifyPropertyChanged(); }
}
public ICommand ClearCommand { get; }
private void Clear(object parameter)
{
Text = string.Empty;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "") =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
In the view, you simply bind to the properties of the view model:
<TextBox AcceptsReturn="True" Name="txtEditor" Text="{Binding Text}" />
<Button Content="Clear" Command="{Binding ClearCommand}" />
Just remember to set the DataContext of the view to an instance of your view model for the bindings to work:
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
I am trying to determine which of my Sliders Invoked the Event, so I can call the OutputAnalogChannel Method with the Index of the Slider and the Slider value.
My Sliders that could potentially invoke the Event are called:
{ K8055AnalogOutputSlider1, K8055AnalogOutputSlider2, [...], K8055AnalogOutputSlidern }
So nothing is wrong with the following code, it works, but I feel like this is a very 'bad' way of solving this problem.
What i was thinking is that some kind of 'additional' integer value is added to the Slider which corresponds to the correct Slider at the Index.
Honestly this answer is probably hiding somewhere on stackoverflow, but I am not sure what I'd be searching for, so i posted here. Thanks in advance!
private void K8055AnalogOutputSliderValueChanged(object sender, RoutedEventArgs e)
{
Slider slider = sender as Slider;
K8055.OutputAnalogChannel(int.Parse(slider.Name[slider.Name.Length - 1].ToString()), (int)slider.Value);
}
You could use the controls' Tag property. Just set the property to the index of the control and then check it in your event handler:
K8055.OutputAnalogChannel((int)slider.Tag, (int)slider.Value);
This is a little more work, but it makes things incredibly easy to modify and maintain and read. It also gets you started taking advantage of some very powerful features of WPF. But if you're under severe deadline pressure, Vincent's quick fix has the virtue of simplicity.
C#
public class ChannelViewModel : INotifyPropertyChanged
{
private string _name = "";
public string Name
{
get { return _name; }
set
{
_name = value;
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(nameof(Name)));
}
}
private int _channel = 0;
public int Channel
{
get { return _channel; }
set
{
_channel = value;
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(nameof(Channel)));
}
}
private int _value = 0;
public int Value
{
get { return _value; }
set
{
_value = value;
K8055.OutputAnalogChannel(Channel, Value);
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(nameof(Value)));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
Channels.Add(new ChannelViewModel { Name="Fred", Channel = 1, Value = 3 });
Channels.Add(new ChannelViewModel { Name="Bob", Channel = 2, Value = 35 });
}
public ObservableCollection<ChannelViewModel> Channels { get; private set; }
= new ObservableCollection<ChannelViewModel>();
public event PropertyChangedEventHandler PropertyChanged;
}
XAML
<ItemsControl
ItemsSource="{Binding Channels}"
BorderBrush="Black"
BorderThickness="1"
>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="2">
<TextBlock>Channel
<Run Text="{Binding Channel, Mode=OneWay}" />:
<Run Text="{Binding Name, Mode=OneWay}" /></TextBlock>
<Slider Value="{Binding Value}" Minimum="1" Maximum="100" Width="300" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Ok here several questions
First
This is my implementation but it is not working atm how can i make it work ?
<Grid> // xaml part
<Button Content="Start Crawling Root Sites - This Deletes All Data" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="279" Command="{Binding StartCrawling}" Click="click_start_Crawling" />
<ListBox Name="lstBoxEvents" HorizontalAlignment="Left" Height="138" Margin="294,10,0,0" VerticalAlignment="Top" Width="312">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="1" Text="{Binding ocEvents}" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
private void click_start_Crawling(object sender, RoutedEventArgs e) // inside mainwindow
{
PublicStaticFunctions.AddMsgToEvents("trial");
}
public static class PublicStaticFunctions // seperate class
{
public static ObservableCollection<string> ocEvents = new ObservableCollection<string>();
public static void AddMsgToEvents(string srMessage)
{
ocEvents.Insert(0, srMessage);
}
}
My second question when running multithreaded, if multiple threads access AddMsgToEvents function and adds variable, would listbox still auto refresh without any problem ?
Thank you very much
This line:
public static ObservableCollection<string> ocEvents = new ObservableCollection<string>();
This is neither a Dependency Property nor a property implementing INotifyPropertyChanged,it's also static, if you use a viewmodel you can do it the way described below, well you may in codebehind to, but it invoke a double redraw. Anyways you need to change it to a DP or prop like below:
private ObservableCollection<string> ocEvents = new ObservableCollection<string>();
public ObservableCollection<string> OcEvents
{
get { return ocEvents; }
set
{
if (Equals(value, ocEvents)) return;
ocEvents = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator] // Comment out if you don't have R#
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
Binding to a static class/property has some weirdities to it, you have to bind differently. I'm off to bed now, so I can't provide and example. In the case above I would have created a vm, and a service for getting my event instead for making my vm unittestable (create an interface of the service providing events, and have the event property on the vm).
AddMsgToEvents()
Must be invoked on the dispatcher thread. A bad hack is to add the following code. Call it with
BeginInvoke(YourMethod);
Add the code to your vm's baseclass, I know the dispatcher should be "used" this way, but for simplicly.
public void BeginInvoke(Action action)
{
if( Application.Current.Dispatcher.CheckAccess())
Application.Current.Dispatcher.BeginInvoke(action);
else
{
action();
}
}
public void Invoke(Action action)
{
if (Application.Current.Dispatcher.CheckAccess())
Application.Current.Dispatcher.BeginInvoke(action);
else
{
action();
}
}
public static class PublicStaticFunctions
You sort of have your service right there, just unstatic it and create an interface of it :D Move your prop to vm/codebehind, or you need some changes to your binding there to, pluss hava a Prop or DP. short answer.
Edit: Example of service
public interface IEventService
{
ObservableCollection<String> GetEvents();
void AddEvent(String eventToAdd);
}
public class EventService : IEventService
{
private readonly ObservableCollection<string> events;
public EventService(ObservableCollection<string> events)
{
this.events = events;
}
public EventService()
{
events = new ObservableCollection<string>();
}
public ObservableCollection<string> Events
{
get { return events; }
//set { events = value; }
}
public ObservableCollection<String> GetEvents()
{
return events;
}
public void AddEvent(String eventToAdd)
{
events.Add(eventToAdd);
}
}
Feed this interface to your vm's ctor(TDD :) ) or codebehind ctor. Allocate and store it in a IOC cotainer, servicelocator or whatever.
Hope it helps,
Cheers,
Stian
I am trying to bind a ViewModel property of type Visibility to the visibility property on a Dock Panel:
Updated ViewModel Code:
public class SelectWaferButtonViewModel : INotifyPropertyChanged
{
private bool isClicked;
public SelectWaferButtonViewModel()
{
isClicked = false;
}
public bool IsControlVisible
{
get
{
return isClicked;
}
set
{
isClicked = value;
OnPropertyChanged("IsControlVisible");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnButtonClick()
{
if (isClicked)
{
IsControlVisible = false;
}
else
{
IsControlVisible = true;
}
}
protected virtual void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
and here is my updated XAML code:
<DockPanel
Name="tvwDockPanel"
Width="200"
Visibility="{Binding IsControlVisible, FallbackValue=Collapsed, Converter={StaticResource BoolToVisConverter}}"
DockPanel.Dock="Left">
<DockPanel
DockPanel.Dock="Top"
Height="22">
</DockPanel>
and I set the data context in the code behind with this line:
tvwDockPanel.DataContext = btnSelectWaferViewModel;
where btnSelectWaferViewModel is the ViewModel object for this situation.
and for fun, here is my code behind:
public partial class WaferTrackerWindow : Window
{
List<ISubscribeEvents> subscriptionList;
SelectWaferButtonViewModel btnSelectWaferViewModel;
public WaferTrackerWindow()
{
InitializeComponent();
this.InstantiateObjects();
this.SubscribeEvents();
this.SetDataContexts();
}
#region Methods
private void SetDataContexts()
{
tvwDockPanel.DataContext = btnSelectWaferViewModel.IsControlVisible;
}
private void SubscribeEvents()
{
foreach (ISubscribeEvents subscriber in subscriptionList)
{
subscriber.SubscribeEvents();
}
}
private void InstantiateObjects()
{
btnSelectWaferViewModel = new SelectWaferButtonViewModel();
subscriptionList = new List<ISubscribeEvents>();
subscriptionList.Add(
new Classes.WaferTrackerWindow.SelectWaferButtonView(btnSelectWafer, btnSelectWaferViewModel));
}
#endregion
}
All I want to do click the button btnSelectWafer and have the tvwDockPanel's visibility property to get to set to Visible via binding. Then when you click again on btnSelectWafer, tvwDockPanel's visibility property gets set back to Collapsed again. tvwDockPanel's visibility will only ever be either Collapsed or Visible.
Any help would be awesome, I am rather new to this whole data binding concept.
You have several issues here:
First of all, the intent of MVVM (if you're trying to do this with MVVM) is to separate logic from presentation. This means that in no way your ViewModel can have a reference to System.Windows.Controls.Button, nor to System.Windows.Visibility, nor to any other classes inside the System.Windows Namespace.
It is not clear to me what your SelectWaferButtonViewModel class is doing with the Button, but you need to remove the Button from there.
Also, If you need to manipulate the Visibility of a control from the ViewModel layer, you'd better use a Boolean property and the BooleanToVisibilityConverter in XAML:
ViewModel:
public bool IsControlVisible {get;set;} //Don't forget INotifyPropertyChanged!!
XAML:
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVisConverter"/>
</Window.Resources>
<DockPanel Visibility="{Binding IsControlVisible, Converter={StaticResource BoolToVisConverter}}"/>
The problem is that you're binding your DockPanel to the boolean property of your view model, and then setting the Visiblity property of your UI element to the IsControlVisible property of the datacontext (which doesn't exist).
Change to:
private void SetDataContexts()
{
tvwDockPanel.DataContext = btnSelectWaferViewModel;
}