I have a panel with a button on it that is used to trigger an image capture from an external camera. The capture can take several seconds, so I want the button to disable when capture is in progress. I also want to be able to prevent the user capturing when my program is running a control script. Here is my ViewModel class:
public class CameraControlViewModel : ViewModelBase
{
public CameraControlViewModel()
{
}
public CameraControlViewModel( DataModel dataModel )
: base( dataModel )
{
dataModel.PropertyChanged += DataModelOnPropertyChanged;
_captureImageCommand = new RelayCommand( captureImage );
_capturedImage = new BitmapImage();
_capturedImage.BeginInit();
_capturedImage.UriSource = new Uri( "Images/fingerprint.jpg", UriKind.Relative );
_capturedImage.CacheOption = BitmapCacheOption.OnLoad;
_capturedImage.EndInit();
}
public ICommand CaptureImageCommand
{
get { return _captureImageCommand; }
}
public bool CanCaptureImage
{
get { return !dataModel.IsScriptRunning && !_captureInProgress; }
}
public bool IsCaptureInProgress
{
get { return _captureInProgress; }
set
{
if (_captureInProgress != value)
{
_captureInProgress = value;
OnPropertyChanged( "IsCaptureInProgress" );
OnPropertyChanged( "CanCaptureImage" );
}
}
}
public int PercentDone
{
get { return _percentDone; }
set
{
if (_percentDone != value)
{
_percentDone = value;
OnPropertyChanged( "PercentDone" );
}
}
}
public BitmapImage CapturedImage
{
get { return _capturedImage; }
}
private void DataModelOnPropertyChanged( object sender, PropertyChangedEventArgs propertyChangedEventArgs )
{
string property = propertyChangedEventArgs.PropertyName;
if (property == "IsScriptRunning")
{
OnPropertyChanged( "CanCaptureImage" );
}
OnPropertyChanged( property );
}
private void captureImage( object arg )
{
IsCaptureInProgress = true;
PercentDone = 0;
// TODO: remove this placeholder.
new FakeImageCapture( this );
// TODO (!)
}
internal void captureComplete()
{
IsCaptureInProgress = false;
}
// Remove this placeholder when we can take images.
private class FakeImageCapture
{
CameraControlViewModel _viewModel;
int _count;
Timer _timer = new Timer();
public FakeImageCapture( CameraControlViewModel viewModel )
{
this._viewModel = viewModel;
_timer.Interval = 50;
_timer.Elapsed += TimerOnTick;
_timer.Start();
}
private void TimerOnTick( object sender, EventArgs eventArgs )
{
++_count;
if (_count <= 100)
{
_viewModel.PercentDone = _count;
}
else
{
Application.Current.Dispatcher.Invoke( (Action)_viewModel.captureComplete );
_timer.Stop();
_timer = null;
_viewModel = null;
}
}
}
private readonly ICommand _captureImageCommand;
private volatile bool _captureInProgress;
private BitmapImage _capturedImage;
private int _percentDone;
}
Here is the XAML for the button:
<Button Command="{Binding CaptureImageCommand}"
Grid.Row="0" Grid.Column="0"
Margin="4"
IsEnabled="{Binding CanCaptureImage}"
ToolTip="Capture Image">
<Image Source="../Images/camera-icon.gif" Width="64" Height="64" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Button>
Clicking the "capture" button goes fine. The button disables and elsewhere a progress bar appears showing the (currently faked) image capture progress. However, when the capture completes, even though I set the CanCaptureImage property in the captureComplete() method, the button does not change back to its "enabled" appearance. It will only do this when I click somewhere (anywhere) in the window. However, the button is actually enabled because I can click on it again to trigger a 2nd capture.
I have tried CommandManager.InvalidateRequerySuggested() inside captureComplete() but that doesn't help. Any ideas?
Rather than having a separate IsEnabled binding to enable/disable the button, you should really just use the CanExecute predicate of the RelayCommand: http://msdn.microsoft.com/en-us/library/hh727783.aspx
This would ensure that the button will get enabled/disabled properly when calling CommandManager.InvalidateRequerySuggested(). Get rid of the CanCaptureImage property and modify your code as follows:
public CameraControlViewModel( DataModel dataModel )
: base( dataModel )
{
dataModel.PropertyChanged += DataModelOnPropertyChanged;
_captureImageCommand = new RelayCommand( captureImage, captureImage_CanExecute );
_capturedImage = new BitmapImage();
_capturedImage.BeginInit();
_capturedImage.UriSource = new Uri( "Images/fingerprint.jpg", UriKind.Relative );
_capturedImage.CacheOption = BitmapCacheOption.OnLoad;
_capturedImage.EndInit();
}
private bool captureImage_CanExecute( object arg)
{
return !dataModel.IsScriptRunning && !_captureInProgress;
}
Related
I have a base class from System.Windows.Controls.Control that changes Visibility, Enabled , Background, Foreground properties according to data from outside.
when I use the class like below
public class RsdDesignBase : Button
{
....
}
It works for Button Control. I want to use same class for other controls like TextBox, Image, TextBlock but if I use like this I neet copy paste same code for all other controls.
Is there a way to use my RsdDesignBase class as base class for others controls ? Or any other way to do this.
I will paste whole class below. What it does is waits for changes in DataTag objects when they change it changes to some properties. For example if _enabledTag.Value is 0 it disables the control.
public class RsdDesignButtonBase : Button
{
private DataTag _visibilityTag;
private DataTag _enabledTag;
private DataTag _appearanceTag;
public TagScriptObject TagScriptObject { get; set; }
private readonly Timer _timer;
protected RsdDesignButtonBase()
{
Loaded += RSD_ButtonBase_Loaded;
Unloaded += OnUnloaded;
_timer = new Timer(1000);
_timer.Elapsed += TimerOnElapsed;
}
private void TimerOnElapsed(object sender, ElapsedEventArgs e)
{
Dispatcher.BeginInvoke(new Action(() =>
{
var background = Background;
var foreground = Foreground;
Background = foreground;
Foreground = background;
}), DispatcherPriority.Render);
}
private void OnUnloaded(object sender, RoutedEventArgs e)
{
if (_enabledTag != null) _enabledTag.DataChanged -= EnabledTagOnDataChanged;
if (_visibilityTag != null) _visibilityTag.DataChanged -= VisibilityTagOnDataChanged;
if (_appearanceTag != null) _appearanceTag.DataChanged -= AppearanceTagOnDataChanged;
}
private void RSD_ButtonBase_Loaded(object sender, RoutedEventArgs e)
{
DependencyPropertyDescriptor desc =
DependencyPropertyDescriptor.FromProperty(FrameworkElement.TagProperty, typeof(FrameworkElement));
desc.AddValueChanged(this, TagPropertyChanged);
TagPropertyChanged(null, null);
}
private void TagPropertyChanged(object sender, EventArgs e)
{
if (Tag == null) return;
TagScriptObject = JsonConvert.DeserializeObject<TagScriptObject>(Tag.ToString());
if (TagScriptObject?.VisibilityProperty?.TagId > 0)
{
_visibilityTag =
GlobalVars.AllDataTagList.FirstOrDefault(t => t.Id == TagScriptObject.VisibilityProperty?.TagId);
if (_visibilityTag != null)
{
_visibilityTag.DataChanged += VisibilityTagOnDataChanged;
VisibilityTagOnDataChanged(null, null);
}
}
if (TagScriptObject?.EnableProperty?.TagId > 0)
{
_enabledTag =
GlobalVars.AllDataTagList.FirstOrDefault(t => t.Id == TagScriptObject.EnableProperty?.TagId);
if (_enabledTag != null)
{
_enabledTag.DataChanged += EnabledTagOnDataChanged;
EnabledTagOnDataChanged(null, null);
}
}
if (TagScriptObject?.AppearanceProperty?.TagId > 0)
{
_appearanceTag =
GlobalVars.AllDataTagList.FirstOrDefault(t => t.Id == TagScriptObject.AppearanceProperty?.TagId);
if (_appearanceTag != null && !_appearanceTag.IsEventHandlerRegistered(null))
{
_appearanceTag.DataChanged += AppearanceTagOnDataChanged;
AppearanceTagOnDataChanged(null, null);
}
}
}
private void AppearanceTagOnDataChanged(object source, EventArgs args)
{
_timer.Enabled = false;
_ = Dispatcher.BeginInvoke(new Action(() =>
{
double tagValue;
bool result = true;
if (_appearanceTag.VarType == VarType.Bit)
{
tagValue = _appearanceTag.TagValue ? 1 : 0;
}
else
{
result = double.TryParse(_appearanceTag.TagValue.ToString(), out tagValue);
}
if (result)
{
foreach (var controlColor in TagScriptObject.AppearanceProperty.ControlColors)
{
if (tagValue >= controlColor.RangeMin &&
tagValue <= controlColor.RangeMax)
{
Background =
new BrushConverter().ConvertFromString(controlColor.Background) as SolidColorBrush;
Foreground =
new BrushConverter().ConvertFromString(controlColor.Foreground) as SolidColorBrush;
_timer.Enabled = controlColor.Flashing == ConfirmEnum.Yes;
break;
}
}
}
}), DispatcherPriority.Render);
}
private void EnabledTagOnDataChanged(object source, EventArgs args)
{
_ = Dispatcher.BeginInvoke(new Action(() =>
{
if (_enabledTag != null)
{
if (TagScriptObject.EnableProperty.IsRangeSelected)
{
double tagValue;
bool result = true;
if (_enabledTag.VarType == VarType.Bit)
{
tagValue = _enabledTag.TagValue ? 1 : 0;
}
else
{
result = double.TryParse(_enabledTag.TagValue.ToString(), out tagValue);
}
if (result)
{
if (tagValue >= TagScriptObject.EnableProperty.RangeFrom &&
tagValue <= TagScriptObject.EnableProperty.RangeTo)
{
IsEnabled = TagScriptObject.EnableProperty.DefaultValue;
}
else
{
IsEnabled = !TagScriptObject.EnableProperty.DefaultValue;
}
}
}
else
{
if (_enabledTag.IsNumeric || _enabledTag.VarType == VarType.Bit)
{
var bitArray = _enabledTag.GetBitArray();
var singleBit = TagScriptObject.EnableProperty.SingleBit;
if (bitArray.Count > singleBit)
{
if (bitArray[singleBit])
{
IsEnabled = TagScriptObject.EnableProperty.DefaultValue;
}
else
{
IsEnabled = !TagScriptObject.EnableProperty.DefaultValue;
}
}
}
}
}
}), DispatcherPriority.Render);
}
private void VisibilityTagOnDataChanged(object source, EventArgs args)
{
_ = Dispatcher.BeginInvoke(new Action(() =>
{
if (_visibilityTag != null)
{
if (TagScriptObject.VisibilityProperty.IsRangeSelected)
{
double tagValue;
bool result = true;
if (_visibilityTag.VarType == VarType.Bit)
{
tagValue = _visibilityTag.TagValue ? 1 : 0;
}
else
{
result = double.TryParse(_visibilityTag.TagValue.ToString(), out tagValue);
}
if (result)
{
if (tagValue >= TagScriptObject.VisibilityProperty.RangeFrom &&
tagValue <= TagScriptObject.VisibilityProperty.RangeTo)
{
Visibility = TagScriptObject.VisibilityProperty.DefaultValue
? Visibility.Visible
: Visibility.Hidden;
}
else
{
Visibility = TagScriptObject.VisibilityProperty.DefaultValue
? Visibility.Collapsed
: Visibility.Visible;
}
}
}
else
{
if (_visibilityTag.IsNumeric || _visibilityTag.VarType == VarType.Bit)
{
var bitArray = _visibilityTag.GetBitArray();
var singleBit = TagScriptObject.VisibilityProperty.SingleBit;
if (bitArray.Count > singleBit)
{
if (bitArray[singleBit])
{
Visibility = TagScriptObject.VisibilityProperty.DefaultValue
? Visibility.Visible
: Visibility.Hidden;
}
else
{
Visibility = TagScriptObject.VisibilityProperty.DefaultValue
? Visibility.Hidden
: Visibility.Visible;
}
}
}
}
}
}), DispatcherPriority.Render);
}
}
If I understand you correctly, you want to add some feature to Button, TextBox, Image and TextBlock (and possibly more) and reuse that code for all classes, right?
What you're doing right now is adding a Base at the bottom of the inheritance tree. That way you can't share it with other classes. Ideally, you would want to change the System.Windows.Controls.Control, but that's part of the .NET Framework, so you can't change that...
This is the downside of inheritance...
The only possibility I see is to use composition:
Create a class containing the logic you want. Let's call it RsdDesign. No superclass needed. It will look a lot like your RsdDesignButtonBase.
Create a descendant for every Control you want to add this feature to
Give those descendants a private member of type ``RsdDesign````.
Connect all applicable methods of the Control to the member.
public class RsdDesign
{
private DataTag _visibilityTag;
private DataTag _enabledTag;
private DataTag _appearanceTag;
public TagScriptObject TagScriptObject { get; set; }
private readonly Timer _timer;
private System.Windows.Controls.Control _parentControl
protected RsdDesign(System.Windows.Controls.Control parentControl)
{
_parentControl = parentControl;
_parentControl.Loaded += RSD_ButtonBase_Loaded;
_parentControl.Unloaded += OnUnloaded;
_timer = new Timer(1000);
_timer.Elapsed += TimerOnElapsed;
}
// The rest of your RsdDesignButtonBase implementation
// ...
}
public class RsdDesignButton: Button
{
private RsdDesign _design;
public RsdDesignButton(...)
{
_design = new RsdDesign(this);
}
// You may need to hook some of the methods explicitly like this:
private void EnabledTagOnDataChanged(object source, EventArgs args)
{
_design.EnabledTagOnDataChanged(source, args);
}
}
I haven't tried this, but maybe the idea helps you to find a solution.
If you derive from your RsdDesignButtonBase class from FrameworkElement:
public class RsdDesignBase : FrameworkElement
{
...
}
...you should be able to extend and customize it for TextBox, Image, TextBlock and any other FrameworkElement, e.g.:
public class TextBlock : RsdDesignBase {}
As far as I can see your control does two(three) things:
It sets a certain layout to the control (visibility, background etc)
it deals a lot with (de)serializing and processing JSON data.
Some of the processing in return modifies UI properties (e.g. Hide/Show) if certain data is available or not.
Following the helpful principal of "separation of concerns" - not because it sound academic or is 'awesome', but because you don't get into a mess of too tightly coupled code - I would much rather recommend to put all of this logic into an Attached Property or a set of Attached properties. And to pass the control as the first argument.
You would not have to change a lot of the implementation and you could use it for virtually all WPF elements that derive from Control or even FrameworkElement
https://learn.microsoft.com/en-us/dotnet/desktop/wpf/advanced/attached-properties-overview?view=netframeworkdesktop-4.8
I am attempting to change a picture when clicking on it in the sidebar menu, but however nothing seem to happen when I click on a image. This is done in WPF and for a image editing program. I have been reading up on custom commands and reading other custom commands posts here on StackOverflow but I can't seem to find anything that apply to this case.
public partial class ImageEditor : UserControl
{
ICommand changePicture;
public ICommand ChangePicture
{
get { return changePicture; }
set { changePicture = value; }
}
public ImageEditor()
{
InitializeComponent();
}
public void loadImages(string _imageFile)
{
string directory = Path.GetDirectoryName(_imageFile);
string[] _filePaths = Directory.GetFiles(directory, "*.jpg");
foreach (string image in _filePaths)
{
if (image.Length > 0)
{
thumbNails.Children.Add(addPictureButton(image));
}
}
}
public Button addPictureButton(string image)
{
Image currentImage = new Image();
currentImage.Source = new BitmapImage(new Uri(image));
currentImage.Stretch = Stretch.Fill;
Grid grid = new Grid();
grid.Height = 90;
grid.Children.Add(currentImage);
var newButton = new MyButton();
newButton.Content = grid;
newButton.Name = "button";
newButton.Height = 100;
newButton.Background = Brushes.Chocolate;
newButton.ImageRef = image;
newButton.Command = ChangePicture;
return newButton;
}
}
I think there is a problem with my custom command, this is the main method where I load all the pictures.
private void Window_Loaded(object sender, RoutedEventArgs e)
{
/// Get the Handle for the Forms System Menu
IntPtr systemMenuHandle = GetSystemMenu(this.Handle, false);
/// Create our new System Menu items just before the Close menu item
InsertMenu(systemMenuHandle, 5, MF_BYPOSITION | MF_SEPARATOR, 0, string.Empty); // <-- Add a menu seperator
InsertMenu(systemMenuHandle, 6, MF_BYPOSITION, _SettingsSysMenuID, "Inställningar...");
InsertMenu(systemMenuHandle, 7, MF_BYPOSITION, _CalibrateSysMenuID, "Kalibrerings Läge (På/Av)");
InsertMenu(systemMenuHandle, 8, MF_BYPOSITION, _ZoomPrevSysMenuID, "Zoom Läge -1");
InsertMenu(systemMenuHandle, 9, MF_BYPOSITION, _ZoomNextSysMenuID, "Zoom Läge +1");
//InsertMenu(systemMenuHandle, 10, MF_BYPOSITION, _PrintSysMenuID, "Print...");
// Attach our WndProc handler to this Window
HwndSource source = HwndSource.FromHwnd(this.Handle);
source.AddHook(new HwndSourceHook(WndProc));
if (ImageFile != null && ImageFile.Length > 0)
{
if (_cameraManager != null)
_cameraManager.IsPaused = true;
Bitmap bitmap = new Bitmap(ImageFile);
UcImageGrabbedControls.SetImageToView(bitmap);
ImagePicker.loadImages(ImageFile);
ImagePicker.ChangePicture = new CustomCommand(OnPictureClick);
BtnGrab.IsToggled = true;
UpdateShownControls();
}
}
Button goes here
public void OnPictureClick(object sender)
{
var button = (UserControls.MyButton)sender;
if (button == null)
return;
var bitmap = new Bitmap(button.ImageRef);
UcImageGrabbedControls.SetImageToView(bitmap);
}
Custom command implementation
public class CustomCommand : ICommand
{
//public delegate void ExecutedMethod();
//private ExecutedMethod method;
private Action<object> onPictureClick;
public CustomCommand(/*ExecutedMethod executed*/)
{
//method = executed;
}
public CustomCommand(Action<object> onPictureClick)
{
this.onPictureClick = onPictureClick;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
//method();
}
}
This class looks like a lot of people tinkered with it until it broke, so I removed everything that is no longer in use anyway.
public class CustomCommand : ICommand
{
private Action<object> onPictureClick;
public CustomCommand(Action<object> onPictureClick)
{
this.onPictureClick = onPictureClick;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
// Nothing happening here :(
}
}
The basic problem is, that the Execute method doesn't do anything - unsurprisingly any button executing this command doesn't do anything either. The simplest fix would be to simply restore functionality to this method by invoking the method passed to the constructor:
public void Execute(object parameter)
{
onPictureClick.Invoke(parameter);
// or onPictureClick(parameter)
}
Since you are already working on that method, I suggest you rename the action to something less specific, e.g _action.
If you ever intend (or need) to make use of the CanExecute feature and/or use the CommandParameter I suggest you take a look at a RelayCommand implementation.
(This answer has a detailed explanation).
This my view Main:
XAML View Main :
<DataGrid AutoGenerateColumns="false"
SelectedItem="{Binding Selectedrole}"
ItemsSource="{Binding RoleList}">
<Button Content="Add Role"
Command="{Binding AddRole}"
Height="35"/>
</DataGrid>
The ViewModel Main:
public RoleManagementViewModel()
{
roleList = new ObservableCollection<UserRoleClass>(WCFclient.GetAllRoles());
_addRole = new RelayCommand<string>(AddRoleFunction);
}
private void AddRoleFunction(object obj)
{
if (!Application.Current.Windows.OfType<SelectionCompartementView>().Any())
{
AddRoleView winAddRole = new AddRoleView();
winAddRole.DataContext = new AddRoleViewModel();
winAddRole.Show();
winAddRole.Topmost = true;
winAddRole.Focus();
}
}
public ObservableCollection<UserRoleClass> RoleList
{
get { return roleList; }
set
{
roleList = value;
OnPropertyChanged("RoleList");
}
}
The view Add Role:
Xaml Add-Role:
<Button x:Name="button1"
Command="{Binding SaveRole}"
CommandParameter="{Binding ElementName=AddRole}"/>
ViewModel Add-Role:
public AddRoleViewModel()
{
_addOrUpdate = new UserRoleClass();
_addOrUpdate = new UserRoleClass();
saveRole = new RelayCommand<Window>(addFunc);
}
private void addFunc(Window window)
{
UserRoleClass newRole = new UserRoleClass()
{
name = AddOrUpdate.name,
description = AddOrUpdate.description,
};
int resultSave = WCFclient.saveRole(newRole);
if (resultSave == 0)
{
String UpdateInformation0 = "Role is saved successfully";
string sCaption = "Save Role";
MessageBoxButton btnMessageBox = MessageBoxButton.OK;
MessageBoxImage icnMessageBox = MessageBoxImage.Information;
MessageBoxResult rsltMessageBox = MessageBox.Show(
UpdateInformation0, sCaption, btnMessageBox, icnMessageBox);
}
if (window != null)
{
window.Close();
}
}
private ICommand saveRole;
public ICommand SaveRole
{
get { return saveRole; }
}
It works fine: when I add a new Role, the view of Add-Role closes and returns to the view Main, and I have a result in database... but not in the DataGrid in MainView.
How can I refresh directly?
First, why do you have two of the following line?
_addOrUpdate = new UserRoleClass();
Second, when you save your new role, it seems you're calling a WCF service that saves it to the DB.
You're using an observable collection which should update when you add to it, but I don't see your code adding the new role into the RoleList.
The fastest way to do it is with something like this.
public AddRoleViewModel(Action<UserRoleClass> onAdded = null)
{
_addOrUpdate = new UserRoleClass();
_addOrUpdate = new UserRoleClass();
_onAdded = onAdded;
saveRole = new RelayCommand<Window>(addFunc);
}
private void addFunc(Window window)
{
UserRoleClass newRole = new UserRoleClass()
{
name = AddOrUpdate.name,
description = AddOrUpdate.description,
};
int resultSave = WCFclient.saveRole(newRole);
if (resultSave == 0)
{
String UpdateInformation0 = "Role is saved successfully";
string sCaption = "Save Role";
MessageBoxButton btnMessageBox = MessageBoxButton.OK;
MessageBoxImage icnMessageBox = MessageBoxImage.Information;
MessageBoxResult rsltMessageBox = MessageBox.Show(UpdateInformation0, sCaption, btnMessageBox, icnMessageBox);
}
}
_onAdded?.Invoke(newRole);
if (window != null)
{
window.Close();
}
And when you create the ViewModel
new AddRoleViewModel(newItem=>{ RoleList.Add(newItem); });
But I can't say I like the architecture all that much. If might want to look at some sort of a messenger service
This is a common problem with binding datagrid to an observable collection. The binding updates if the collection changes not if the collection content changes. The best way to do this, after adding the new UserRole to the RoleList is to use a "deep" observable collection like this one:
public sealed class DeepObservableCollection<T>
: ObservableCollection<T> where T : INotifyPropertyChanged {
public event PropertyChangedEventHandler ItemPropertyChanged;
public DeepObservableCollection() {
CollectionChanged += DeepObservableCollection_CollectionChanged;
}
public DeepObservableCollection(ObservableCollection<T> collection)
: base(collection) {
CollectionChanged += DeepObservableCollection_CollectionChanged;
}
public DeepObservableCollection( List<T> collection )
: base( collection ) {
CollectionChanged += DeepObservableCollection_CollectionChanged;
}
void DeepObservableCollection_CollectionChanged( object sender, NotifyCollectionChangedEventArgs e ) {
if ( e.NewItems != null ) {
foreach ( var item in e.NewItems ) {
var notifyPropertyChanged = item as INotifyPropertyChanged;
if ( notifyPropertyChanged != null ) notifyPropertyChanged.PropertyChanged += item_PropertyChanged;
}
}
if ( e.OldItems != null ) {
foreach ( Object item in e.OldItems ) {
var notifyPropertyChanged = item as INotifyPropertyChanged;
if ( notifyPropertyChanged != null ) notifyPropertyChanged.PropertyChanged -= item_PropertyChanged;
}
}
}
void item_PropertyChanged( object sender, PropertyChangedEventArgs e ) {
NotifyCollectionChangedEventArgs a = new NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction.Reset );
OnCollectionChanged( a );
ItemPropertyChanged?.Invoke( sender, e );
}
}
your UserRoleClass must implement INotifyPropertyChanged (maybe using ObservableObject) but that's all what is required. When you add a new user role datagrid updates through binding.
I have an error "Must create DependencySource on same Thread as the DependencyObject" in my project.
My comment is used to load a file and create a list. This list is bind to a ListBox. AL was working good. But I created a Task to load (load can be long). Now I have this error. I don't understand why it occurs.
There is my code :
MainView.xaml:
<ListBox ItemsSource="{Binding Results}"
SelectedItem="{Binding SelectedItem}">
<ListBox.InputBindings>
<KeyBinding Command="{Binding RemoveCommand}"
Key="Delete"/>
</ListBox.InputBindings>
</ListBox>
<Button Grid.Row="1" Grid.Column="0"
Style="{StaticResource StyleButton}"
Command="{Binding LoadCommand}"
Content="Open result"/>
MainViewModel:
#region Fields/Properties
public ImageWithPoints SelectedItem
{
get
{
return _selectedItem;
}
set
{
_selectedItem = value;
SelectedPointIndex = 1;
OnPropertyChanged();
OnPropertyChanged("Picture");
UpdatePoints();
}
}
public List<ImageWithPoints> Results
{
get
{
return _results;
}
set
{
_results = value;
if (value == null)
{
SelectedPointIndex = 0;
}
OnPropertyChanged();
}
}
public BitmapSource Picture
{
get
{
return SelectedItem?.Picture;
}
}
#endregion
#region Load
private ICommand _loadCommand;
public ICommand LoadCommand
{
get
{
if (_loadCommand == null)
_loadCommand = new RelayCommand(OnLoad, CanLoad);
return _loadCommand;
}
}
public void OnLoad()
{
StartRunning(this, null);
Task loadTask = new Task(new Action(() =>
{
Load();
Application.Current.Dispatcher.Invoke(new Action(() =>
{
StopRunning(this, null);
}));
}));
loadTask.Start();
}
public bool CanLoad()
{
return !IsRunning;
}
#endregion
#region Events
public event EventHandler OnStartRunning;
public event EventHandler OnStopRunning;
private void StartRunning(object sender, EventArgs e)
{
OnStartRunning(sender, e);
}
private void StopRunning(object sender, EventArgs e)
{
OnStopRunning(sender, e);
}
#enregion
#region Methods
public void Load()
{
// Open File
// Set to list
List<ImageWithPoints> listRes;
Results = listRes;
SelectedItem = Results[0];
}
#endregion
When I remove the line SelectedItem = Results[0]; I have no error (but application don't work has it should).
Set the SelectedItem property back on the UI thread once the Task has finished:
public void OnLoad()
{
StartRunning(this, null);
Task.Factory.StartNew(new Action(() =>
{
Load();
})).ContinueWith(task =>
{
SelectedItem = Results[0];
StopRunning(this, null);
}, System.Threading.CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());
}
You can only access a UI element on the thread on which it was originally created so if your UpdatePoints() method accesses any control you must call this method on the UI thread.
I have an Image element that's bound to an ImageSource element inside a class that I've created. The ImageSource gets updated every time a slider is changed. When I first instantiate my window, the ImageSource is blank until the user loads a file. Once the file is loaded, the image appears and the user can scroll the slider and see the image change. They can then select "OK" on the dialog to save this pattern. This all works fine.
However, if they double-click on the item in the ListView then it will re-open this dialog to make further edits. So, it creates a new dialog and then reloads the pertinent info about the image. However, for whatever reason... the image binding no longer works. I can put a breakpoint on the ImageSource getter and everytime I change the slider, the image does get updated... However, it just doesn't appear the be binding correctly. Why would it bind correctly on the first time the window is opened, but not on subsequent openings. I'll try to lay out my code.
In my .XAML code:
<UserControl x:Class="MyControls.CreatePattern"
x:Name="PatternCreation"
...
d:DesignHeight="160" d:DesignWidth="350">
<Slider Value="{Binding ElementName=PatternCreation, Path=Pattern.ZNorm, Mode=TwoWay}" Maximum="1" Name="Slider" VerticalAlignment="Stretch" />
<Image Name="PatternPreview" Source="{Binding ElementName=PatternCreation, Path=Pattern.WPFSlice}" Stretch="Uniform"></Image>
</UserControl
In my code behind I define the Pattern to be bound:
protected PatternVoxelBased mPattern = new PatternVoxelBased();
public PatternVoxelBased Pattern
{
get { return mPattern ; }
set { mPattern = value; }
}
In my PatternVoxelBased class, I have a WPFSlice and ZNorm properties defined like this:
protected ImageSource mWPFSlice;
public ImageSource WPFSlice
{
get { return mWPFSlice; }
set
{
mWPFSlice = value;
NotifyPropertyChanged("WPFSlice");
}
}
protected double mZNorm = 0.5;
public double ZNorm
{
get { return mZNorm; }
set
{
if (mZNorm == value) return;
mZNorm = value;
NotifyPropertyChanged("ZNorm");
WPFSlice = BuildImageAtZ(mZNorm);
}
}
I have an event to load the dialog window the first time:
private void CreatePattern_Click(object sender, RoutedEventArgs e)
{
CCreateVoxelPattern dlg = new CCreateVoxelPattern();
dlg.DataContext = DataContext;
dlg.CShow(PatternLibraryMenu);
}
My ListView Double-Click function to reload the dialog window:
private void ListViewPatternLibrary_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
PatternVoxelBased item = ((ListView)sender).SelectedValue as PatternVoxelBased;
CCreateVoxelPattern dlg = new CCreateVoxelPattern();
dlg.DataContext = DataContext;
dlg.Main.Pattern = item;
dlg.Main.LoadPattern();
dlg.CShow(PatternLibraryMenu);
}
public void LoadPattern()
{
if (Pattern == null) return;
Pattern.WPFSlice = Pattern.BuildImageAtZ(Pattern.ZNorm);
}
In your class where this is
protected PatternVoxelBased mPattern = new PatternVoxelBased();
public PatternVoxelBased Pattern
{
get { return mPattern ; }
set { mPattern = value; }
}
you have to implement INotifyPropertyChanged.
Example
public class YourClass: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
{
PropertyChanged(this, e);
}
}
protected PatternVoxelBased mPattern = new PatternVoxelBased();
public PatternVoxelBased Pattern
{
get { return mPattern ; }
set { mPattern = value; OnPropertyChanged(new PropertyChangedEventArgs("Pattern"));}
}
}
EDIT
In your Pattern-class, you have to implement that too on every Property.