Well, I've tried so many different things so hopefully I wont mess up to explain what I've tried so far.
As sidenote I'm working with Caliburn Micro and trying to follow the MVVM Pattern.
I'm trying to reorder the columns of a datagrid programmatically when the ViewModel is loaded (so the user can save their settings for the grid).
The Binding of the column properties is working and reflect their current values.
To test the behavior I've set up a Dictionary<int,string> with the DisplayIndexes and the ColumnNames in a special order.
public class OrderSumUpViewModel : Screen, IHandle<DialogEvent>, IHandle<LogOutEvent>
{
public BindableCollection<UIProcessStepModel> ProcessSteps { get; set; }
public BindableCollection<UIOrderModel> OrdersBc { get; set; }
private List<Predicate<UIOrderModel>> criteria = new List<Predicate<UIOrderModel>>();
private ISaveAndLoadSettings settings;
private IEventAggregator events;
private IWindowManager manager;
private ModifyStatus modifyStatus;
private bool IsCancelationPlausibleThroughDialog;
private string cancelMessage;
public Dictionary<int,string> ColPositions;
public OrderSumUpViewModel(IEventAggregator events, IWindowManager manager, ISaveAndLoadSettings settings) {
this.settings = settings;
this.events = events;
this.manager = manager;
this.events.Subscribe(this);
this.OrdersBc = new BindableCollection<UIOrderModel>(new UIAllOrdersModel().GetAll());
this.Orders = CollectionViewSource.GetDefaultView(OrdersBc);
this.ProcessSteps = new BindableCollection<UIProcessStepModel>(UIAllProcessStepsModel.GetAll());
this.modifyStatus = new ModifyStatus();
SetInitialValues();
}
private void SetInitialValues() {
this.ColPositions = new Dictionary<int, string>()
{
[0] = "ColPosDue",
[1] = "ColPosCode",
[2] = "ColPosStatus",
[3] = "ColPosMat",
[4] = "ColPosProgress",
[5] = "ColPosQuantity",
[6] = "ColPosUnit",
[7] = "ColPosItemCode",
[8] = "ColPosItemName",
[9] = "ColPosOrderDate",
[10] = "ColPosExpectedFinishDate",
[11] = "ColPosOrderedByEmp",
[12] = "ColPosDepartment",
[13] = "ColPosProcessSteps",
[14] = "ColPosAdditions",
[15] = "ColPosProject",
[16] = "ColPosCommission"
};
if (ActiveUserModel.GetAdminPermission()) {
IsCancelButtonVisible = true;
}
deleteFilter();
}
private ICollectionView orders;
public ICollectionView Orders
{
get
{
return this.orders;
}
set
{
this.orders = value;
NotifyOfPropertyChange(() => Orders);
}
}
#region Column Properties
public int ColPosDue
{
get { return GetColPosition("ColPosDue"); }
set
{
ReorderColPositions(value, "ColPosDue");
}
}
public int ColPosStatus
{
get { return GetColPosition("ColPosStatus"); }
set
{
ReorderColPositions(value, "ColPosStatus");
}
}
public int ColPosMat
{
get { return GetColPosition("ColPosMat"); }
set
{
ReorderColPositions(value, "ColPosMat");
}
}
public int ColPosProgress
{
get { return GetColPosition("ColPosProgress"); }
set
{
ReorderColPositions(value, "ColPosProgress");
}
}
public int ColPosCode
{
get { return GetColPosition("ColPosCode"); }
set
{
ReorderColPositions(value, "ColPosCode");
}
}
public int ColPosOrderDate
{
get { return GetColPosition("ColPosOrderDate"); }
set
{
ReorderColPositions(value, "ColPosOrderDate");
}
}
public int ColPosOrderedByEmp
{
get { return GetColPosition("ColPosOrderedByEmp"); }
set
{
ReorderColPositions(value, "ColPosOrderedByEmp");
}
}
public int ColPosDepartment
{
get { return GetColPosition("ColPosDepartment"); }
set
{
ReorderColPositions(value, "ColPosDepartment");
}
}
public int ColPosProcessSteps
{
get { return GetColPosition("ColPosProcessSteps"); }
set
{
ReorderColPositions(value, "ColPosProcessSteps");
}
}
public int ColPosExpectedFinishDate
{
get { return GetColPosition("ColPosExpectedFinishDate"); }
set
{
ReorderColPositions(value, "ColPosExpectedFinishDate");
}
}
public int ColPosQuantity
{
get { return GetColPosition("ColPosQuantity"); }
set
{
ReorderColPositions(value, "ColPosQuantity");
}
}
public int ColPosUnit
{
get { return GetColPosition("ColPosUnit"); }
set
{
ReorderColPositions(value, "ColPosUnit");
}
}
public int ColPosItemCode
{
get { return GetColPosition("ColPosItemCode"); }
set
{
ReorderColPositions(value, "ColPosItemCode");
}
}
public int ColPosItemName
{
get { return GetColPosition("ColPosItemName"); }
set
{
ReorderColPositions(value, "ColPosItemName");
}
}
public int ColPosAdditions
{
get { return GetColPosition("ColPosAdditions"); }
set
{
ReorderColPositions(value, "ColPosAdditions");
}
}
public int ColPosProject
{
get { return GetColPosition("ColPosProject"); }
set
{
ReorderColPositions(value, "ColPosProject");
}
}
public int ColPosCommission
{
get { return GetColPosition("ColPosCommission"); }
set
{
ReorderColPositions(value, "ColPosCommission");
}
}
#endregion
#region Rearrange Columns
private int GetColPosition(string columnName) {
return ColPositions.FirstOrDefault(x => x.Value == columnName).Key;
}
#endregion
}
}
The Code above is one of my first tries when I was on the step to save the correct columnorder in a dictionary. But once the loading of the column values begins the values of the dictionary is switched around and wont fit to the original values anymore and so the column order of the user is messed up. Im missing an idea to solve that. Initially the values may not change until the datagrid is completely loaded then the values may change...
Another try was to set backing fields for the properties and just call a method at start to grab the wanted display indexes out of the dictionary. Since the automatic reordering happens I've tried to reflect the actual property state and compare it to the display index in the dictionary and redo the setting of each column till its ordered in the right way.
private void SetInitialValues() {
this.ColPositions = new Dictionary<int, string>()
{
[0] = "ColPosDue",
[1] = "ColPosCode",
[2] = "ColPosStatus",
[3] = "ColPosMat",
[4] = "ColPosProgress",
[5] = "ColPosQuantity",
[6] = "ColPosUnit",
[7] = "ColPosItemCode",
[8] = "ColPosItemName",
[9] = "ColPosOrderDate",
[10] = "ColPosExpectedFinishDate",
[11] = "ColPosOrderedByEmp",
[12] = "ColPosDepartment",
[13] = "ColPosProcessSteps",
[14] = "ColPosAdditions",
[15] = "ColPosProject",
[16] = "ColPosCommission"
};
if (ActiveUserModel.GetAdminPermission()) {
IsCancelButtonVisible = true;
}
deleteFilter();
SetAllColumnPositions();
}
#region private Members of Column Properties
private int colPosDue;
private int colPosStatus;
...more columns follow
#endregion
public int ColPosDue
{
get { return colPosDue; }
set
{
colPosDue = value;
}
}
public int ColPosStatus
{
get { return colPosStatus; }
set
{
colPosStatus = value;
}
}
...more column properties follow
private void SetAllColumnPositions() {
bool isOrderRearranged;
foreach (int column in ColPositions.Keys.ToList()) {
SetActualColumnOrder();
isOrderRearranged = true;
do {
if (ActualColPositions[column] != ColPositions[column]) {
switch (ColPositions[column]) {
case "ColPosDue":
colPosDue = column;
NotifyOfPropertyChange(() => ColPosDue);
isOrderRearranged = false;
break;
//...for each column
default:
break;
}
}
} while (isOrderRearranged);
}
}
private void SetActualColumnOrder() {
Type type = this.GetType();
ActualColPositions = new Dictionary<int, string>();
for (int i = 0; i <= 16; i++) {
ActualColPositions.Add(i, "");
}
foreach (PropertyInfo prop in type.GetProperties()) {
if (prop.Name.StartsWith("ColPos")) {
ActualColPositions[int.Parse(prop.GetValue(this).ToString())] = prop.Name;
}
}
}
I'v tried to do these steps starting in a reverse order when setting the new display indexes (starting from position no 16) and so on.
After the display indexes have set in the correct way via the constructer invocation the column properties are set once again and I really dont know why and when else I should set the indexes.
Thats the final value...
Any suggestion would be highly appreciated.
Thanks!
I am building an app with WPF and Caliburn.Micro. I want to update a ProgressBar from an Task/Thread and I am wondering what I need to correctly update the UI:
public class DemoViewModel : PropertyChangedBase
{
private int m_Progress;
public int Progress
{
get { return m_Progress; }
set
{
if (value == m_Progress) return;
m_Progress = value;
NotifyOfPropertyChange();
NotifyOfPropertyChange(nameof(CanStart));
}
}
public bool CanStart => Progress == 0 || Progress == 100;
public void Start()
{
Task.Factory.StartNew(example);
}
private void example()
{
for (int i = 0; i < 100; i++)
{
Progress = i + 1; // this triggers PropertChanged-Event and leads to the update of the UI
Thread.Sleep(20);
}
}
}
From other programming languages I know that I need to synchronize with the UI thread to update the UI but my code just works. Is there something I missed and which could cause sporadic errors or is there some magic behind the scenes which care of the synchronization?
It will depend on how you've implemented INotifyPropertyChanged. The implementation should delgate all UI updates to the appropriate dispatcher.
Sample Implementation:
public void RaisePropertyChanged([CallerMemberName]string name) {
Application.Current.Dispatcher.Invoke(() => {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}, System.Windows.Threading.DispatcherPriority.Background);
}
Also to clean up the task start a bit:
Edit:
Removed unnecessary bool return value, and set ConfigureAwait to stay off UI thread when task completes.
public async void Start()
{
await Task.Run(() => example()).ConfigureAwait(false);
}
private async Task example()
{
for (int i = 0; i < 100; i++)
{
Progress = i + 1; // this triggers PropertChanged-Event and leads to the update of the UI
await Task.Delay(20);
}
}
The CurrentSelectedBall is updated whenever I changed its value due to two-way binding.
When I click to update the function UpdateRedBall is called so the redball in the database is updated. But the view list of balls is not updated as the ObservableCollection<RedBall> RedBalls is not changed at all.
How to fix this problem? I guess something needs to be done after _context.SaveChanges();
Also I can not simply do DataGridA.itemsSource = RedBalls to make a hard refresh here as first DataGridA is not accessible in the MainviewModel.
Some of the methods:
public ObservableCollection<RedBall> RedBalls
{
get { return new ObservableCollection<RedBall>(_context.RedBalls.OrderBy(m=>m.DateNumber));}
set
{
_redBalls = value;
RaisePropertyChanged("RedBalls");
}
}
public RedBall CurrentSelectedRedBall
{
get { return _currentSelectedRedBall; }
set
{
_currentSelectedRedBall = value;
RaisePropertyChanged("CurrentSelectedRedBall");
}
}
private void UpdateRedBall()
{
if (CurrentSelectedRedBall != null)
{
var ballToUpdate = _context.RedBalls.SingleOrDefault(x => x.Id == CurrentSelectedRedBall.Id);
ballToUpdate.DateNumber = CurrentSelectedRedBall.DateNumber;
ballToUpdate.First = CurrentSelectedRedBall.First;
ballToUpdate.Second = CurrentSelectedRedBall.Second;
ballToUpdate.Third = CurrentSelectedRedBall.Third;
ballToUpdate.Fourth = CurrentSelectedRedBall.Fourth;
ballToUpdate.Fifth = CurrentSelectedRedBall.Fifth;
ballToUpdate.Sixth = CurrentSelectedRedBall.Sixth;
}
_context.SaveChanges();
//RedBalls = RedBalls
}
I have a strong dislike for getters that 'do stuff'. It should be impossible for a get to fail. I would consider moving the logic in your RedBalls getter elsewhere. I'm also concerned that you're newing up the RedBalls ObservableCollection on every get as it can cause problems with bindings. Bound ObservableCollections should be instantiated once, then altered using Add, Remove and Clear. Working with them this way should also solve the problem of updating your collection appropriately.
public ObservableCollection<RedBall> RedBalls
{
get { return _redBalls; }
set
{
_redBalls = value;
RaisePropertyChanged("RedBalls");
}
}
public RedBall CurrentSelectedRedBall
{
get { return _currentSelectedRedBall; }
set
{
_currentSelectedRedBall = value;
RaisePropertyChanged("CurrentSelectedRedBall");
}
}
private void UpdateCurrentSelectedRedBall()
{
UpdateRedBall();
var redBalls = _context.RedBalls.OrderBy(m=>m.DateNumber);
if(redBalls != null)
{
RedBalls.Clear();
foreach(RedBall rb in redBalls)
{
RedBalls.Add(rb);
}
}
}
private void UpdateRedBall()
{
if (CurrentSelectedRedBall != null)
{
var ballToUpdate = _context.RedBalls.SingleOrDefault(x => x.Id == CurrentSelectedRedBall.Id);
ballToUpdate.DateNumber = CurrentSelectedRedBall.DateNumber;
ballToUpdate.First = CurrentSelectedRedBall.First;
ballToUpdate.Second = CurrentSelectedRedBall.Second;
ballToUpdate.Third = CurrentSelectedRedBall.Third;
ballToUpdate.Fourth = CurrentSelectedRedBall.Fourth;
ballToUpdate.Fifth = CurrentSelectedRedBall.Fifth;
ballToUpdate.Sixth = CurrentSelectedRedBall.Sixth;
}
_context.SaveChanges();
}
Use something like this: https://gist.github.com/itajaja/7507120 - basically you need to subscribe for PropertyChanged and raise CollectionChanged events from that.
In a ListView, I want the selected items to be always visible (in the current field of view).
Example:
I have six items in my ListView. Only (the top) five are visible.
I select the first item. When I scroll down, this item is not visible anymore, but it remains selected.
I want to DEselect any item that goes out of the current view.
I solved it manually, so it's more a workaround.
I just keep track of the index...
public class ScrollIndexManager
{
private readonly int _viewableItemsCount;
private int _canScrollUpCount;
private int _canScrollDownCount;
public ScrollIndexManager(int viewableItemsCount)
{
_viewableItemsCount = viewableItemsCount;
}
public bool HasChanged { get; set; }
public int BottomVisibleItemIndex
{
get { return TopVisibleItemIndex + _viewableItemsCount - 1; }
}
private int _topVisibleItemIndex = 0;
public int TopVisibleItemIndex
{
get { return _topVisibleItemIndex; }
set
{
if (value < 0)
{
HasChanged = false;
return;
}
_topVisibleItemIndex = value;
HasChanged = true;
}
}
public void SetCanScrollDown(int totalItemCount)
{
_canScrollDownCount = totalItemCount - _viewableItemsCount;
}
public bool CanScrollUp()
{
return _canScrollUpCount > 0;
}
public bool CanScrollDown()
{
return _canScrollDownCount > 0;
}
public bool Increase()
{
TopVisibleItemIndex--;
return HasChanged;
}
public bool Decrease()
{
TopVisibleItemIndex++;
return HasChanged;
}
public void ScrolledUp()
{
_canScrollUpCount--;
_canScrollDownCount++;
}
public void ScrolledDown()
{
_canScrollUpCount++;
_canScrollDownCount--;
}
}
My code is here>>
public class Player:INotifyPropertyChanging
{
string addressBar;
public string Url
{
get {
return addressBar;
}
set { addressBar = value; OnPropertyChanged("Url"); }
}
public Regex regVillage = new Regex(#"\?doc=\d+&sys=[a-zA-Z0-9]{2}");
RelayCommand _AddAttackTask;
public ICommand AddAttackTask
{
get {
if (_AddAttackTask == null)
{
_AddAttackTask = new RelayCommand(param =>
{
}, param => this.CanAttack);
}
return _AddAttackTask;
}
}
public Boolean CanAttack
{
get{
if (Url == null) return false;
return regVillage.IsMatch(Url);
}
}
}
On the xaml, i have textbox and button. Textbox binded by url, button binded by AddAttackTask. When i change textbox value,Url changed.Main target is When changing url, button bring to enable or disable. But button always disabled.
I'm getting RelayCommand class from WPF Apps With The Model-View-ViewModel Design Pattern
What is wrong on my code?
Please fix my command binding!
I found it yourself.
Must call CommandManager.InvalidateRequerySuggested(); function after changing property
public string Url
{
get {
return addressBar;
}
set { addressBar = value; OnPropertyChanged("Url");
CommandManager.InvalidateRequerySuggested();
}
}