(See Solution below)
Struggling learning MVVMLight and WPF. Happy to get page navigation with a frame with 3 buttons and also get the CanExecute RelayCommand functionality working for the buttons.
However, when I change pages with the Back and Forward button from the frame, the frame navigation buttons seem to not raise the propertychanged for the Frame Source Property FrameURI in the viewmodel. In the RelayCommand execute I'm looking at the FrameURI property to determine button command can execute. I'm thinking that I need to raisepropertychange of FrameURI property in the viewmodel after a Frame FWD or BACK button press. It looks like I can do that in a Navigated or LoadCompleted EVENT from the NavigationService Class. Is this the best way to go about this? Maybe there's an easier way?
PROPER BEHAVIOR HERE:
NOT WORKING CORRECTLY HERE when the frame BACK button is pressed. The page changes (good), but the button Middle canexecute didn't update correctly. See property and relay commands further below.
MVVMINPCPROPERTY snippet
public const string FrameUriPropertyName = "FrameUri";
private Uri _frameUri;
/// <summary>
/// Sets and gets the FrameUri property.
/// Changes to that property's value raise the PropertyChanged event.
/// </summary>
public Uri FrameUri
{
get
{
return _frameUri;
}
set
{
Set(FrameUriPropertyName, ref _frameUri, value);
}
}
XAML for Frame
<Frame x:Name="MainFrameDS" Source="{Binding FrameUri}" HorizontalAlignment="Left" Height="211" Margin="109,88,0,0" VerticalAlignment="Top" Width="258"/>
RelayCommand example
private RelayCommand _changeToLastPage;
/// <summary>
/// Gets the ChangeToLastPage.
/// </summary>
public RelayCommand ChangeToLastPage
{
get
{
return _changeToLastPage
?? (_changeToLastPage = new RelayCommand(
() =>
{
FrameUri = ViewModelLocator.LastPageUri;
},
() => FrameUri != ViewModelLocator.LastPageUri));
}
}
I have the source code on github and using Win7 and VS2017 Community. I also have other issues w/ the program and opened issues on github. Any help is appreciated.
My goal is to have a few basic MVVMLight program 'templates' that I can pull from and share them w/ other learners like me... Thanks.
Solution
Add Mode=twoway to the binding per #steve-teece.
Did some DEBUG.writeline (Break point suggestion per #Michael-Randall) to the output window and found out that the URI for the page was different depending on if it referred back to the viewmodellocater.IntroPageUri or called by the frame FWD or BACK button.
The two URI results were:
/IntroPage.xaml OR IntroPage.xaml
My assumption was that the 'non-fore slash' version didn't equal the version with a '/'.
I'm not sure of the best way to solve this so I converted the URI's to strings and compared them w/ the string Contains method in a boolean method.
I replaced the follow line:
FrameUri != ViewModelLocator.IntroPageUri
It was replaced with a method call:
CheckUri(FrameUri, ViewModelLocator.IntroPageUri)
And, the CheckUri method:
private Boolean CheckUri(Uri _frameUriToCheck, Uri _vmUri)
{
string StringUriToCheck = _frameUriToCheck.ToString();
string StringUriVM = _vmUri.ToString();
System.Diagnostics.Debug.WriteLine(StringUriToCheck, "StringUriToCheck");
System.Diagnostics.Debug.WriteLine(StringUriVM, "StringUriVM");
if (StringUriVM.Contains(StringUriToCheck))
{ return false; }
else
{ return true; }
}
That worked! If someone has a better way to solve it, I'm all ears.
Thanks all for the feedback!
try changing the XAML to
<Frame x:Name="MainFrameDS" Source="{Binding FrameUri, Mode=TwoWay}" HorizontalAlignment="Left" Height="211" Margin="109,88,0,0" VerticalAlignment="Top" Width="258"/>
Firstly, make sure you are referencing
GalaSoft.MvvmLight.CommandWpf;
and not
using GalaSoft.MvvmLight.Command;
If your CanExcute is not getting evaluated you might need to manually raise the change via RaiseCanExecuteChanged
public Uri FrameUri
{
get
{
return _frameUri;
}
set
{
if(Set(FrameUriPropertyName, ref _frameUri, value))
{
// assuming this is the command you are having trouble with
// this will force the command to reevaluate CanExecute
ChangeToLastPage.RaiseCanExecuteChanged();
}
}
}
Update
Omg i didnt even notice this
public const string FrameUriPropertyName = "FrameUri";
you need to at least be setting this properly
Set(() => FrameUri, ref _frameUri, value);
Related
I've created an example to illustrate my problem.
ViewModel:
public class VM : INotifyPropertyChanged
{
private double _value = 1;
public double Value
{
get { return _value; }
set
{
_value = value;
OnPropertyChanged();
}
}
public VM()
{
var timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromTicks(1);
timer.Tick += (s, e) => { Value += 1; };
timer.Start();
}
// OnPropertyChanged stuff ...
}
}
View:
<Window.DataContext>
<namespace:VM/>
</Window.DataContext>
<Grid>
<TextBox Text="{Binding Value, IsAsync=True, FallbackValue=Test}"/>
</Grid>
When running my application the text in the textbox flickers. During the update process the FallbackValue is displayed, which makes no sense to me.
Does anyone knows the purposes or what are the benefits that during the update process the FallbackValue is displayed? Is there a way to display the old Value during an async update process?
This seems normal to me, given that you are using IsAsync=True in your binding. From the documentation:
While waiting for the value to arrive, the binding reports the FallbackValue, if one is available
When the PropertyChanged event is raised, WPF initiates the process of updating the target of the binding. Normally this would happen synchronously, with the property getter called immediately to update the value.
But you are using IsAysnc=True, so instead WPF fills in the target with the fallback value, and starts an asynchronous request to retrieve the actual property value later. Until that request has completed, the fallback value is displayed.
Does anyone knows the purposes or what are the benefits that during the update process the FallbackValue is displayed?
Per the documentation, the intent behind the IsAsync=True setting is that it's used when the property getter is, or could be, slow. Your code has told WPF that the property value has changed, so it knows the old value is no longer valid. Your code has also told (via the IsAsync in the XAML) that the property getter could take some time to provide the new value, so it defers retrieving that value until later.
In the meantime, what should WPF display? That's what the fallback value is there for.
Is there a way to display the old Value during an async update process?
If you don't want the behavior that is designed for this feature in WPF, you should just retrieve the new data asynchronously yourself, and update the property via the setter when you have it. It's not a good idea for a property getter to be slow anyway, so this would be a better design in any case.
I had the same problem but with an image source. I've removed the IsAsync on the binding and I have made my getter async:
// This field hold a copy of the thumbnail.
BitmapImage thumbnail;
// Boolean to avoid loading the image multiple times.
bool loadThumbnailInProgress;
// I'm using object as the type of the property in order to be able to return
// UnsetValue.
public object Thumbnail
{
get {
if (thumbnail != null) return thumbnail;
if (!loadThumbnailInProgress) {
// Using BeginInvoke() allow to load the image asynchronously.
dispatcher.BeginInvoke(new Action(() => {
thumbnail = LoadThumbnail();
RaisePropertyChanged(nameof(Thumbnail));
}));
loadThumbnailInProgress = true;
}
// Returning UnsetValue tells WPF to use the fallback value.
return DependencyProperty.UnsetValue;
}
}
Sometimes a binding will fail, failure is important to consider. Fallback value option presents users a message if an error occurs, rather than nothing happening. If you would like your fallbackvalue to display the previous value contained, I could think of a few ways of trying : possibly saving the value in a reference string and/or to another control, then binding to that control
But if you don't want the fallbackvalue displayed at all, you need to do a code inspection to see how your binding is failing/or is slow, and contain it in your code behind
I've found an approach to avoid flickering by just inheriting from textbox and overriding it's textproperty-metadata.
Custom TextBoxControl
public class CustomTextBox : TextBox
{
static CustomTextBox()
{
TextProperty.OverrideMetadata(typeof(CustomTextBox), new FrameworkPropertyMetadata(null, null, CoerceChanged));
}
private static object CoerceChanged(DependencyObject d, object basevalue)
{
var tb = d as TextBox;
if (basevalue == null)
{
return tb.Text;
}
return basevalue;
}
}
View
<Window.DataContext>
<namespace:VM/>
</Window.DataContext>
<Grid>
<namespace:CustomTextBox Text="{Binding Value, IsAsync=True}"/>
</Grid>
It's important to have the text-binding without a fallbackvalue. So during update process the text is set to the textproperty defalut value - so in this case to null.
The CoerceChanged handler checks whether the new value is null. If it's so he returns the old value so that during update process there is still the old value displayed.
So having a play with PRISM and I have a grid who's Visibility property is bound to a property in a view model like so.
Xaml
Grid Grid.RowSpan="2" Grid.ColumnSpan="3" Background="#7F808080" Visibility="{Binding LoadingVisibility}">
Code Behind
private Visibility loadingVisibility = Visibility.Visible;
public Visibility LoadingVisibility
{
get
{
return loadingVisibility;
}
set
{
SetProperty(ref loadingVisibility, value);
}
}
Now if I do this LoadingVisibility = Visibility.Collapsed;, the grid does not disappear and is still visible.
If I then set a breakpoint at SetProperty(ref loadingVisibility, value); I can see the original value of loadingVisibility, which is set as Visibile, and I can see that value is set to Collapsed.
If I then step on I can see loadingVisiblity has now changed to collapsed as it should. At this point I expect the Grid to be notified which in turn executes 'Get' to retrieve the value. This does not occur.
The binding is working because the Get is called when loading up and if I change private Visibility loadingVisibility = Visibility.Visible; to Collapsed and run the code the grid starts invisible.
So my question is, after SetProperty is executed, why is the Get not?
EDIT:
Just so you can see where I set the property.
public ShellViewModel(IEventAggregator IEventAggregator)
{
IEventAggregator.GetEvent<PubSubEvent<HardwareLoaded>>().Subscribe(x =>
{
if (!x.HardwareOK)
{
MessageBox.Show("There was an issue loading hardware. See Log");
}
LoadingVisibility = Visibility.Collapsed;
});
}
EDIT 2:
Just found something interesting, if I comment out LoadingVisibility = Visibility.Collapsed; in the Subscribe and then add a button to the xaml and have the click event like so then everything works fine.
private void Button_Click(object sender, RoutedEventArgs e)
{
mvm.LoadingVisibility = Visibility.Collapsed;
}
So now I guess the question is why, when both methods call the set property, does only one fully work and cause the Get to work?
Quite often when using the EventAggregator, you're working on the UI thread. But seeing the HardwareLoaded type it came to me that you might be doing some checking on another thread. And as we know, bindings have to be updated from the UI thread. Normally you would use Dispather.BeginInvoke, but Prism's EventAggregator has an overload in the Subscribe method to tell the handler to offload to the UI thread.
IEventAggregator.GetEvent<PubSubEvent<HardwareLoaded>>().Subscribe(x =>
{
if (!x.HardwareOK)
{
MessageBox.Show("There was an issue loading hardware. See Log");
}
LoadingVisibility = Visibility.Collapsed;
}, ThreadOption.UIThread);
I am trying to execute a bound command from my code behind utilizing the UiElement. button.Command.Execute(button.CommandParameter)
However, at this point the Command property of the button is null. simultaneously when I check the command in my View Model the property is set. The only diagnosis I can come up with is that until the window is actually visible the command is not bound to the command property of the button. I feel like may I'm missing a step somewhere or my implementation is not sound. below is some snipits of the code, please let me know if you need more.
Window constructor:
public PlottingViewModel ViewModel { get; set; }
public PlottingGUI()
{
InitializeComponent();
DataContext = (ViewModel = new PlottingViewModel());
_setDefaultSelections();
}
IList<RadioButton> buttons;
Setting default selections:
private void _setDefaultSelections()
{
buttons = new List<RadioButton>();
_getRadioButtons(this);
foreach (var setting in ViewModel.Settings.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public))
{
var settingValue = setting.GetValue(ViewModel.Settings);
var button = buttons.FirstOrDefault(btn => btn.Content.Equals(settingValue)
|| ((string)btn.CommandParameter).Equals(settingValue));
if (button == null)
continue;
button.IsChecked = true;
// NullReference here
// button.Command.Execute(button.CommandParameter);
}
}
one of the RadioButtons XAML:
<RadioButton Content="None"
Grid.Row="0"
Command="{Binding StampedCommand}"
CommandParameter="None"
Foreground="WhiteSmoke"/>
I feel, the only way i may be able to successfully complete this task is to execute the command directly from my viewmodel. (Which i don't want to do)
Thanks for reading..
To sum up comments at the point when you're calling _setDefaultSelections() bindings have not been updated yet, hence Command is still null, so you have to wait until everything is loaded. You can call _setDefaultSelections during Loaded event
Occurs when the element is laid out, rendered, and ready for interaction.
In my SL4 app, I'm trying to trigger a Command from a button. The Command code is standard stuff that I have used without issue elsewhere, but I cannot get the Command to be called when I click the button.
This was driving me mental, so I eventually created a test page that had nothing on it but a button. The data context of the page is set to my ViewModel, and the ViewModel has an ICommand property on it. The DataContext is working as I can bind a textbox to a string property in the ViewModel. It's so basic, I can include all the relevant stuff here:
From the XAML:
xmlns:models="clr-namespace:x3.ViewModels"
...
<UserControl.DataContext>
<models:TestViewModel/>
</UserControl.DataContext>
<Button x:Name="TestButton" Command="{Binding TestCommand}" Content="AAAAGHH" />
From the ViewModel:
public class TestViewModel:INotifyPropertyChanged
{
ICommand _testCommand;
public ICommand TestCommand
{
get
{
_testCommand = new DelegateCommand(
commandParameter =>
{
var testButton = commandParameter as Button;
},
(commandParameter) => {return true;});
return _testCommand;
}
}
}
The DelegateCommand is part of Telerik.Windows.Controls. If I put a breakpoint at
_testCommand = new DelegateCommand
it gets hit when the page loads, but after that, I can click the button until my mouse wears out, and the command is never called.
For the sake of my mental health, I'd appreciate any help on offer.
Thanks
Mick
The get accessor for TestCommand is only called once - when the binding engine binds the Command of the Button to the TestCommand property. Putting your breakpoint on the first line of the get, it should be expected that it only gets hit once.
What you need to do is put your breakpoint on the code that executes when your command gets run. In your original example, this means break inside the delegate - i.e. on return true.
Edit: you can force the debugger to break in code as well using System.Diagnostics.Debugger.Break():
ICommand _testCommand;
public ICommand TestCommand
{
get
{
_testCommand = new DelegateCommand(
commandParameter =>
{
var testButton = commandParameter as Button;
},
(commandParameter) =>
{
System.Diagnostics.Debugger.Break(); // Force debugger to break
return true;
}
);
return _testCommand;
}
}
In order to isolate the problem, try using your own simple ICommand implementation, instead of Telerik's DelegateCommand.
If the problem still happens, you'll know it's somewhere around your XAML / data binding, and then I'd suggest you post more complete parts of your code so that someone here may help you.
If the problem disappears, you'll know it's something in Telerik DelegateCommand that decides not to call your lambda.
If you can post a more complete reproduction of the problem, it may also help. Because you posted only a minimal part of your code, and maybe the cause of the problem is missing from here...
I am building a small wpf app in C#. When a button gets clicked a third
party dll function constructs a tree like object. This object is bound
to a treeview. This works fine but takes a bit of time to load. As the
dll function constructs the object it prints progress info to the
console. I want to redirect this into a TextBlock so that the user
gets to see the progress messages.
My window ctor looks like this:
InitializeComponent();
StringRedir s = new StringRedir(ref ProgressTextBlock);
Console.SetOut(s);
Console.SetError(s);
this.DataContext = s;
xaml:
<TextBlock Text="{Binding Path=Text}" Width="244"
x:Name="ProgressTextBlock" TextWrapping="Wrap" />
<TreeView >...</TreeView>
The StringRedir class is shown below. The problem is the TextBlock for
some reason does not get updated with the messages until the TreeView
gets loaded. Stepping through I see the Text property being updated
but the TextBlock is not getting refreshed. I added a MessageBox.Show
() at the point where Text gets updated and this seems to cause the
window to refresh each time and I am able to see each message. So I
guess I need some way to explicitly refresh the screen...but this
doesnt make sense I thought the databinding would cause a visual
refresh when the property changed. What am I missing here? How do I
get it to refresh? Any advice is appreciated!
public class StringRedir : StringWriter , INotifyPropertyChanged
{
private string text;
private TextBlock local;
public string Text {
get{ return text;}
set{
text = text + value;
OnPropertyChanged("Text");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
public StringRedir(ref TextBlock t)
{
local = t;
Text = "";
}
public override void WriteLine(string x)
{
Text = x +"\n";
//MessageBox.Show("hello");
}
}
You haven't included the code that is loading the data for the TreeView, but I'm guessing it's being done on the UI thread. If so, this will block any UI updates (including changes to the TextBlock) until it has completed.
So after doing some reading on the WPF threading model ( http://msdn.microsoft.com/en-us/library/ms741870.aspx ) I finally got it to refresh by calling Dispatcher Invoke() with Dispatch priority set to Render. As Kent suggested above UI updates in the dispatcher queue were probably low priority. I ended up doing something like this.
XAML
<TextBox VerticalScrollBarVisibility="Auto"
Text="{Binding Path=Text, NotifyOnTargetUpdated=True}"
x:Name="test" TextWrapping="Wrap" AcceptsReturn="True"
TargetUpdated="test_TargetUpdated"/>
C# target updated handler code
private void test_TargetUpdated(object sender, DataTransferEventArgs e)
{
TextBox t = sender as TextBox;
t.ScrollToEnd();
t.Dispatcher.Invoke(new EmptyDelegate(() => { }), System.Windows.Threading.DispatcherPriority.Render);
}
Note: Earlier I was using a TextBlock but I changed to a TextBox as it comes with scrolling
I still feel uneasy about the whole flow though. Is there a better way to do this?
Thanks to Matt and Kent for their comments. If I had points would mark their answers as helpful.
I believe the problem is in the constructor of your StringRedir class. You're passing in ProgessTextBlock, and you're doing this to it:
local.Text = "";
This is effectively overwriting the previously set value for ProgressTextBlock.Text, which was this:
{Binding Text}
See what I mean? By explicitly setting a value to the TextBlock's Text property, you've cancelled the binding.
If I'm reading right, it looks like the idea of passing a TextBlock into the StringRedir's ctor is a hangover from before you tried binding directly. I'd ditch that and stick with the binding idea as it's more in the "spirit" of WPF.