coolercargo coolercargo - 1 month ago 7
C# Question

textblock not updating when property changed in model

My textblock is not being updated update the value from my Model. If I update the textblock in the ViewModel it works so my bindings seem to be correct. I believe the problem is the way I update it in the Model but I am not sure why also my observableCollection only updates because I pass the values back and forth not sure that is good MVVM strategy.

XAML portion:

<Grid>
<TextBox x:Name="NewLabelBx" HorizontalAlignment="Left" Height="23" Margin="54,449,0,0" TextWrapping="Wrap" Text="{Binding NewLabel,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top" Width="314"/>
<Button x:Name="NewLabelBtn" Content="Add Label" HorizontalAlignment="Left" Margin="293,490,0,0" VerticalAlignment="Top" Width="75" RenderTransformOrigin="0.518,-0.709" Command="{Binding Path=NewLabelBtn}" />
<TextBlock x:Name="FilesProcessedBlck" HorizontalAlignment="Left" Margin="54,507,0,0" TextWrapping="Wrap" Text="{Binding FilesProcessedBlck, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top" RenderTransformOrigin="-0.7,0.562" Width="65"/>
</Grid>


ViewModel portion:

public class P4LabelBatteryViewModel : BindableBase
{
private P4LabelBatteryModel p4LabelBatteryModel = new P4LabelBatteryModel();

public P4LabelBatteryViewModel()
{
P4LabelBatteryModel p4LabelBatteryModel = new P4LabelBatteryModel();

this.GetBatteryBtn = new DelegateCommand(chooseFile, canChooseFile);
this.NewLabelBtn = new DelegateCommand(chooseNewLabel, canNewLabel).ObservesProperty(() => NewLabel);
this.FilesProcessedBlck = 2; //this works.
}

//other code here

private void chooseNewLabel()
{
if (ScriptCollection.Count > 0)
{
ScriptCollection = P4LabelBatteryModel.TagsFilesModel(NewLabel, ScriptCollection);
}
}


private int _filesProcessedBlck;
public int FilesProcessedBlck
{
get
{
return _filesProcessedBlck;
}
set
{
SetProperty(ref _filesProcessedBlck, value);
}

}

private ObservableCollection<ScriptModel> _scriptCollection = new ObservableCollection<ScriptModel>();
public ObservableCollection<ScriptModel> ScriptCollection
{
get
{
return _scriptCollection;
}
set
{
SetProperty(ref _scriptCollection, value);
}

}

}


Model portion:

class P4LabelBatteryModel
{

public static ObservableCollection<ScriptModel> TagsFilesModel(string NewLabel, ObservableCollection<ScriptModel> observableCollection)
{
string newLabel = NewLabel;
var scriptsToTagColl = observableCollection;
string[] files = null;

var _p4LabelBatteryViewModel = new P4LabelBatteryViewModel();
_p4LabelBatteryViewModel.FilesProcessedBlck++; //xaml is never updated with this value.

//This will generate an IPC when returned
ObservableCollection<ScriptModel> newCollection = new ObservableCollection<ScriptModel>();

//code here that modifies newCollection xaml updates when this returns, _p4LabelBatteryViewModel.FilesProcessedBlck++; does not.

return newCollection;
}
}


When I run the debugger I can see the P4LabelBatteryViewModel.FilesProcessedBlck is being modified but the XAML is not being updated.

Answer
    var _p4LabelBatteryViewModel = new P4LabelBatteryViewModel();
    _p4LabelBatteryViewModel.FilesProcessedBlck++;  //xaml is never updated with this value.

OK, so your XAML must have a copy of the viewmodel, if that TextBlock ever displays what you expect in the first place. But then in this method, you create a new instance of the same viewmodel class, set a property on it, and then you do nothing else with it. It goes out of scope and the garbage collector eats it eventually. It was never the DataContext of any view, anywhere, so of course it has no effect on the UI.

_p4LabelBatteryViewModel is a local variable. Nobody outside that method ever sees it or even knows it exists. If you want to change the copy of the viewmodel that's actually being displayed in the UI, you have to change that instance of it. Also, please don't prefix local variables with _. By convention, a leading underscore indicates a private field belonging to a class. It's best to stick with that convention, to avoid confusion.

The viewmodel should update its own FilesProcessedBlck property. It's not a good idea in any case for the model to be responsible for maintaining the viewmodel's state. That's the viewmodel's problem, let him deal with it.

private void chooseNewLabel()
{
    if (ScriptCollection.Count > 0)
    {
        ScriptCollection = P4LabelBatteryModel.TagsFilesModel(NewLabel, ScriptCollection);
        ++FilesProcessedBlck;
    }
}

And in the model...

public static ObservableCollection<ScriptModel> TagsFilesModel(string NewLabel, IList<ScriptModel> scriptsToTagColl)
{
    string newLabel = NewLabel;
    string[] files = null;

    //  This will generate an IPC when returned
    ObservableCollection<ScriptModel> newCollection = new ObservableCollection<ScriptModel>();

    //code here that modifies newCollection  xaml updates when this returns, _p4LabelBatteryViewModel.FilesProcessedBlck++; does not.

    return newCollection;
}

I made a couple of other minor changes to simplify TagsFilesModel. For example, there's no reason for it to require callers to pass in an ObservableCollection<T>. You may never have any reason to give it anything else, but if you make a habit of that kind of flexibility in your code, it pays off.

One more item. This is harmless, but worth knowing:

<TextBlock 
    x:Name="FilesProcessedBlck" 
    HorizontalAlignment="Left" 
    Margin="54,507,0,0" 
    TextWrapping="Wrap" 
    Text="{Binding FilesProcessedBlck}" 
    VerticalAlignment="Top" 
    RenderTransformOrigin="-0.7,0.562" 
    Width="65"
    />

UpdateSourceTrigger=PropertyChanged doesn't serve any purpose in that Binding. The "source" of a binding is the viewmodel property; the "target" is the UI control property. UpdateSourceTrigger=PropertyChanged tells the Binding to update the viewmodel property whenever the control property changes. That seems silly, but you can also set it to UpdateSourceTrigger=LostFocus; TextBox.Text defaults to LostFocus because the usual case with a TextBox is that the user types for a while, but you really don't care about updating your viewmodel until he finishes typing and changes focus to another control. Updating a viewmodel property can have a lot of side effects, so if you update the bound viewmodel property every time the Text changes, in some cases you can end up with pathological behavior: Every time the user types a character, a whole lot of code gets thrown into motion, so much that the UI bogs down. Thus LostFocus.

That's all off-topic for this question, because that's not a TextBox. It's a TextBlock, which can't update the source property at all, so that flag will have no effect.

By the way, what does "Blck" mean? Is that because it's displayed in a TextBlock? What happens if add another place in the UI where it's shown, but the new one is a labe;; should you then rename it FilesProcessedBlckAndLbl? Better to call it FilesProcessedCount and keep the viewmodel out of the business of caring what the UI does.