I have the following XAML:
<StackPanel Orientation="Vertical">
<TextBox IsReadOnly="True" Text="{Binding SchedulerStatus, Mode=OneWay}" Width="150" VerticalAlignment="Center" Margin="10" />
<Button Width="75" Height="30" Content="Test" Command="{Binding StartScheduler}" />
</StackPanel>
This is in a Window bound to this view model:
public class SchedulerViewModel : ViewModelBase // ViewModelBase implements INotifyPropertyChanged, using the [CallerMemberName] attribute.
{
private readonly SchedulerServiceClient _proxy;
public SchedulerViewModel()
{
_proxy = new SchedulerServiceClient();
SchedulerStatusPoller poller = new SchedulerStatusPoller(this, _proxy);
}
private SchedulerStatus _schedulerStatus;
internal SchedulerStatus SchedulerStatus
{
get
{
return _schedulerStatus;
}
set
{
if (value != _schedulerStatus)
{
_schedulerStatus = value;
OnPropertyChanged();
}
}
}
}
SchedulerServiceClient is a proxy to a WCF service that runs continually, and has a Status property that I need to watch. Because I cannot get callbacks from WCF to work after two solid days trying, I have implemented SchedulerStatusPoller, that periodically polls the WCF status, and updates the viewmodel status, in the hope that the display of the WCF status will be updated.
class SchedulerStatusPoller
{
private static readonly Timer StatusTimer = new Timer(5000);
private static SchedulerViewModel viewModel;
private static SchedulerServiceClient proxy;
public SchedulerStatusPoller(SchedulerViewModel targetViewModel, SchedulerServiceClient proxy)
{
SchedulerStatusPoller.proxy = proxy;
viewModel = targetViewModel;
StatusTimer.Elapsed += StatusTimerElapsed;
StatusTimer.AutoReset = true;
StatusTimer.Enabled = true;
StatusTimer.Start();
}
void StatusTimerElapsed(object sender, ElapsedEventArgs e)
{
viewModel.SchedulerStatus = proxy.GetStatus();
}
}
I have used the following code directly in the UI (the Window) that confirms that PropertyChanged is being raised by the SchedulerViewModel. The exception is thrown.
void _viewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
throw new NotImplementedException();
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
((SchedulerViewModel)DataContext).SchedulerStatus = SchedulerStatus.Processing;
}
The poller does call into the SchedulerStatus property on the viewmodel every five seconds, but the textbox does not update. What am I doing wrong?
You have to mention the source trigger
Text="{Binding SchedulerStatus, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
Your OnPropertyChanged inside the Setter is missing an argument. Shouldn't it be sth. like
PropertyChanged("SchedulerStatus");
maybe,you can code it like this:
OnPropertyChanged(this,new PropertyChangedEventArgs("SchedulerStatus"));
Related
I'm pretty sure I've missed something super tiny but I cant for the life of me figure out where I went wrong.
I'm trying to bind my datagrid to the ConsoleLines property which is infact getting input to it, I debugged it and the ConsoleLines contains multiple strings.
But for some reason its not updating the UI and adding the lines to the gridview.. Or well technically the TextBlock.
So I have my MainWindows setup like this
public partial class MainWindow : Window
{
Server Server = new Server();
public MainWindow()
{
InitializeComponent();
DataContext = new MasterViewModel();
}
private void BtnStart_OnClick(object sender, RoutedEventArgs e)
{
Server.StartServer();
}
}
As you can see I am setting the DataContext to a new instance of the MasterViewModel which looks like this
public class MasterViewModel
{
public Server Server { get; }
= new Server();
}
And here is my Server class
public class Server : INotifyPropertyChanged
{
public Process pServer;
public Server()
{
}
public ObservableCollection<string> ConsoleLines { get; }
= new ObservableCollection<string>();
public void StartServer()
{
pServer = new Process();
pServer.StartInfo.FileName = "java";
pServer.StartInfo.Arguments = #"-jar " + "-Xms512M -Xmx1G spigot.jar";
pServer.StartInfo.UseShellExecute = false;
pServer.StartInfo.RedirectStandardOutput = true;
pServer.StartInfo.RedirectStandardError = true;
pServer.StartInfo.RedirectStandardInput = true;
pServer.OutputDataReceived += OKDataReceived;
pServer.ErrorDataReceived += ErrorDataReceived;
pServer.Start();
pServer.BeginErrorReadLine();
pServer.BeginOutputReadLine();
}
private void ErrorDataReceived(object sender, DataReceivedEventArgs e)
=> Application.Current.Dispatcher.Invoke(() => ConsoleLines.Add($"ERROR: {e.Data}"));
private void OKDataReceived(object sender, DataReceivedEventArgs e)
=> Application.Current.Dispatcher.Invoke(() => ConsoleLines.Add($"MESSAGE: {e.Data}"));
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
As far as I know everything is setup correctly I can't see anything that is wrong.
And here is my XAML
<DataGrid ItemsSource="{Binding Server.ConsoleLines}" Width="400" Margin="182,109,210,68">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"></TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<Button Name="BtnStart" Click="BtnStart_OnClick" Content="Start Server" HorizontalAlignment="Left" Margin="365,388,0,0" VerticalAlignment="Top" Width="75"/>
EDIT
Initializing a new instance of the server and then using the Button to start the method on a new instance seem to be the issue.. I dont know why though.
You should remove the Server field from your MainWindow and start the Server that is inside your DataContext.
private void BtnStart_OnClick(object sender, RoutedEventArgs e)
{
((MasterViewModel)DataContext).Server.StartServer();
}
Alternatively, initialize the view model like this:
DataContext = new MasterViewModel { Server = Server };
which assigns the Server field value to the Server property in your view model (which must then be read/write). Otherwise you've got two Server instances.
What am I doing wrong?
I have a Class Model.cs that has my DataContext
I have a Button and a TextBlock next to it. I have tried binding and implementing INotifyPropertyChanged.
When the button is clicked it calls a method that uses WinForms to look for a folder location and display it in the TextBlock
but it does not update. If I debug I get the path correctly.
Any help much appreciated.
MainWindow.xaml
<Button Name="projectLocationBtn"
Width="150"
Height="30"
Click="projectLocationBtn_Click">
<StackPanel Orientation="Horizontal">
<fa:FontAwesome Icon="FolderOpen" Margin="0 0 10 0" />
<TextBlock Text="Select Location" />
</StackPanel>
</Button>
<StackPanel Orientation="Horizontal" Margin="20 0 0 0">
<fa:FontAwesome Icon="AngleRight" Margin="0 0 10 0"/>
<TextBlock Width="800"
TextAlignment="Left"
TextWrapping="NoWrap"
Text="{Binding ProjectLocation}"/>
</StackPanel>
MainWindow.xaml.cs
using M = MercuryTemplateGenerator.Model;
public MainWindow()
{
InitializeComponent();
DataContext = new M.Model();
}
private void projectLocationBtn_Click(object sender, RoutedEventArgs e)
{
M.Model m = new M.Model();
m.GetLocation();
}
Model Class
using Winforms = System.Windows.Forms;
namespace MercuryTemplateGenerator.Model
{
public class Model: INotifyPropertyChanged
{
string _projectLocation;
string _projectName;
public Model() {}
public string ProjectName
{
get {
return _projectName; }
set {
_projectName = value;
OnPropertyChanged("ProjectName");
}
}
public string ProjectLocation
{
get {
return _projectLocation; }
set {
_projectLocation = value;
OnPropertyChanged("ProjectLocation");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string property)
{
PropertyChanged?.Invoke(this, new
PropertyChangedEventArgs(property));
}
public void GetLocation()
{
// get path to desktop
var startPath =
Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
Winforms.FolderBrowserDialog folderDialog = new
Winforms.FolderBrowserDialog();
folderDialog.ShowNewFolderButton = false;
folderDialog.SelectedPath = startPath;
Winforms.DialogResult pathResult = folderDialog.ShowDialog();
if (pathResult == Winforms.DialogResult.OK)
{
_projectLocation = folderDialog.SelectedPath;
}
}
}
}
Many thanks.
The mistake is you have one instance of Model for dataContext of the page and have another one instance you're calling inside projectLocationBtn_Click. If a view is bounded to dataContext it means it's special instance of class lays under view and view will get new data from there. You need to call GetLocation method on the same instance of Model. For example, you can save your first model to field.
_dataContext = new M.Model();
DataContext = _dataContext;
And then use this instance inside handler
private void projectLocationBtn_Click(object sender, RoutedEventArgs e)
{
_dataContext.GetLocation();
}
I can see that after all, it won't work because you don't call OnPropertyChanged("ProjectLocation").
For calling it you have to call setter of ProjectLocation property
Replace:
_projectLocation = folderDialog.SelectedPath;
with
ProjectLocation = folderDialog.SelectedPath;
And for your info: Check how can Button's click be bound to DataContext with Binding work inside XAML file.
https://www.codeproject.com/Articles/238657/How-to-use-Commands-in-WPF
In the GetLocation function you need to set the ProjectLocation property to raise the PropertyChanged event, if you set directly the _projectLocation private field the event won't be raised because it is inside the setter of the property
I'm creating an UWP app that is supposed to get some data from an API and display it, that happens on the "Refresh()" method.
Everything works fine until I try to do a search, that is the "Search()" method.
The "Search()" method is called from the MainPage.
public sealed partial class MarvelMenu : Page, INotifyPropertyChanged
{
//Backing field.
private ObservableCollection<Character> _marvelCharacters = new ObservableCollection<Character>();
//Property
public ObservableCollection<Character> MarvelCharacters
{
get { return _marvelCharacters; }
set
{
if (value != _marvelCharacters)
{
_marvelCharacters = value;
//Notify of the change.
NotifyPropertyChanged();
}
}
}
//PropertyChanged event.
public event PropertyChangedEventHandler PropertyChanged;
//PropertyChanged event triggering method.
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private ObservableCollection<ComicBook> MarvelComics;
public MarvelMenu()
{
this.InitializeComponent();
MarvelComics = new ObservableCollection<ComicBook>();
}
private async void Page_Loaded(object sender, RoutedEventArgs e)
{
var storageFile = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///VoiceCommandDictionary.xml"));
await VoiceCommandDefinitionManager.InstallCommandDefinitionsFromStorageFileAsync(storageFile);
Refresh();
}
public async void Refresh()
{
MyProgressRing.Visibility = Visibility.Visible;
MyProgressRing.IsActive = true;
ErrorTextBlock.Text = "";
MarvelCharacters.Clear();
while (MarvelCharacters.Count < 20)
{
Task t = MarvelFacade.PopulateMarvelCharactersAsync(MarvelCharacters);
await t;
}
try
{
this.MasterListBox.SelectedIndex = 0;
}
catch (Exception)
{
return;
}
MyProgressRing.IsActive = false;
MyProgressRing.Visibility = Visibility.Collapsed;
ErrorTextBlock.Text = MarvelFacade.errorMessage;
var attribute = await MarvelFacade.GetCharacterDataWrapperAsync();
var myAttribute = attribute.attributionText;
try
{
AttributeTextBlock.Text = myAttribute;
}
catch (Exception)
{
return;
}
}
public async void Search(string searchedCharacter)
{
MyProgressRing.Visibility = Visibility.Visible;
MyProgressRing.IsActive = true;
ErrorTextBlock.Text = "";
MarvelCharacters.Clear();
Task t = MarvelFacade.PopulateMarvelCharactersByNameAsync(searchedCharacter, MarvelCharacters);
await t;
MyProgressRing.IsActive = false;
MyProgressRing.Visibility = Visibility.Collapsed;
ErrorTextBlock.Text = MarvelFacade.errorMessage;
}
While running the app in debug mode I found that the C# code runs perfectly and actually retrieves the searched data from the API, however it is not displayed.
Even though I see Visual Studio go through each step in that method none of it is actually displayed.
<ListBox Name="MasterListBox"
ScrollViewer.VerticalScrollBarVisibility="Hidden"
ItemsSource="{x:Bind MarvelCharacters}"
Grid.RowSpan="3"
IsHitTestVisible="True"
SelectionChanged="MasterListBox_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate x:DataType="data:Character">
<ListBoxItem Style="{StaticResource ListBoxItemStyle}"
Margin="-12,-11,-12,-13"
IsHitTestVisible="False">
<StackPanel Orientation="Horizontal">
<Ellipse Width="40"
Height="40"
Margin="4">
<Ellipse.Fill>
<ImageBrush ImageSource="{x:Bind thumbnail.small}"/>
</Ellipse.Fill>
</Ellipse>
<TextBlock Text="{x:Bind name}"
VerticalAlignment="Center"
Width="180"
TextTrimming="WordEllipsis"
FontSize="15"
Margin="10,0,0,0"/>
</StackPanel>
</ListBoxItem>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
My ListBox is binding to "MarvelCharacters" that is an public ObservableCollection properties. So it displays fine when the app is launched but it does not refresh to show the search results.
Any one can help me I would really appreciate it.
The binding in Xaml has a property known as 'Mode'. This property defines the way it is binded.
There a 3 Modes : OneTime,OneWay,TwoWay
OneTime : using will let u bind ur data to ur UI only once and that is during intialization. after that the UI is on its own. x:Bind has default mode setup to OneTime While classic binding ( {Binding} ) has default to setup to OneWay
OneWay : Using this will ensure Your UI Updates every time the Data Updates.
{x:Bind name,Mode=OneWay}
{Binding name,Mode=OneWay}
while Classic binding doesn't require the explicit declaration you can simply bind but for Compiled Binding Explicit declaration is Necessary.
I've created my own socket class and an instance of it in MainWindow.xaml.cs, and I want to create a small little TextBlock to monitor the connection status. I've been using this specific link: WPF textblock binding in XAML
Here's the code attempt. ComUplink.cs:
public class ComUplink
{
public String ConnectionStatus = "Idle";
public Socket Socklink;
}
In MainWindow.xaml.cs:
public partial class MainWindow : Window
{
ComUpLink Uplink;
...
public void Login_Click(object Sender, RoutedEventArgs e)
{
Uplink = new ComUpLink();
}
}
AND in the XAML file:
<TextBlock x:Name="textBlock3"
TextAlignment="Right"
HorizontalAlignment="Left"
Margin="12,218,0,0"
TextWrapping="Wrap"
Text="{Binding Path=Uplink.ConnectionString}"
VerticalAlignment="Top"
Foreground="#616161"
Width="236"/>
So, my question is, why isn't this binding properly? Am I missing an implementation of INotifyPropertyChanged?
Well you made three little mistakes:
You can only bind to properties (if those values change use INotifyPropertyChanged)
You need to set the DataContext
Your Binding used the wrong property name (ConnectionString instead of ConnectionStatus)
Try those modifications:
in MainWindow.xaml.cs:
public void Login_Click(object Sender, RoutedEventArgs e)
{
this.DataContext = new ComUpLink();
}
in ComUplink.cs:
public class ComUplink : INotifyPropertyChanged
{
private String connectionStatus = "Idle";
public String ConnectionStatus
{
get
{
return this.connectionStatus;
}
set
{
this.connectionStatus = value;
this.OnPropertyChanged();
}
}
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public Socket Socklink;
}
in MainWindow.xaml:
<TextBlock x:Name="textBlock3"
TextAlignment="Right"
HorizontalAlignment="Left"
Margin="12,218,0,0"
TextWrapping="Wrap"
Text="{Binding Path=ConnectionStatus}"
VerticalAlignment="Top"
Foreground="#616161"
Width="236"/>
You first need to set the data context of the text block to be the main window or a property.
Second you need to bind to a public property not to field
I have MainWindow containing a datagrid and a "filter panel". The filter panel can change by a user input(button click). I try to achieve it with databinding. The problem that Im facing is the filter panel(which is a user control) is not loaded or refreshed.
Mainwindow xaml:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="250*" />
<ColumnDefinition Width="253*" />
</Grid.ColumnDefinitions>
<DataGrid AutoGenerateColumns="True" Height="200" HorizontalAlignment="Left" Margin="23,28,0,0" Name="dataGrid1" VerticalAlignment="Top" Width="200" ItemsSource="{Binding OverviewableItems}" />
<UserControl Grid.Column="1" Content="{Binding UserControl}" DataContext="{Binding}" Grid.ColumnSpan="2" />
<Button Content="PersonFilter" Height="23" HorizontalAlignment="Left" Margin="23,268,0,0" Name="buttonPersonFilter" VerticalAlignment="Top" Width="75" Click="buttonPersonFilter_Click" />
<Button Content="ProjectFilter" Height="23" HorizontalAlignment="Left" Margin="132,268,0,0" Name="buttonProjectFilter" VerticalAlignment="Top" Width="75" Click="buttonProjectFilter_Click" />
</Grid>
code behind:
private ViewModel _viewModel;
public MainWindow()
{
_viewModel = new ViewModel(new DataProvider());
DataContext = _viewModel;
_viewModel.PropertyChanged += _viewModel.SetFilterType;
InitializeComponent();
}
private void buttonProjectFilter_Click(object sender, RoutedEventArgs e)
{
_viewModel.OverviewType = OverviewType.Project;
}
private void buttonPersonFilter_Click(object sender, RoutedEventArgs e)
{
_viewModel.OverviewType = OverviewType.Person;
}
First user control:
<Grid>
<DatePicker Grid.Column="1" Grid.Row="1" Height="25" HorizontalAlignment="Left" Margin="19,18,0,0" Name="datePickerFundingTo" VerticalAlignment="Top" Width="115" Text="{Binding ElementName=ProjectFilter, Path=FundingTo}" />
</Grid>
code behind for this user control is only this:
public DateTime FundingTo { get; set; }
public ProjectFilter()
{
FundingTo = DateTime.Now;
InitializeComponent();
}
Other user control: just simply contains a TextBox and a Button, for the sake of simplicity I didnt add any code behind to it.
ViewModel of the MainWindow:
public class ViewModel : INotifyPropertyChanged
{
private UserControl _userControl;
public UserControl UserControl
{
get { return _userControl; }
set
{
if (_userControl == value)
{
return;
}
OnPropertyChanged("UserControl");
_userControl = value;
}
}
private OverviewType _overviewType = OverviewType.None;
public OverviewType OverviewType
{
get { return _overviewType; }
set
{
if (_overviewType == value)
{
return;
}
OnPropertyChanged("OverviewType");
_overviewType = value;
}
}
private ObservableCollection<IOverviewItem> _overviewableItems;
public ObservableCollection<IOverviewItem> OverviewableItems
{
get { return _overviewableItems; }
set
{
if (_overviewableItems == value)
{
return;
}
_overviewableItems = value;
}
}
private readonly DataProvider _dataProvider;
public event PropertyChangedEventHandler PropertyChanged;
public ViewModel(DataProvider dataProvider)
{
_dataProvider = dataProvider;
}
public void SetFilterType(object sender, EventArgs eventArgs)
{
switch (_overviewType)
{
case OverviewType.Project:
_userControl = new ProjectFilter();
break;
case OverviewType.Person:
_userControl = new PersonFilter();
break;
}
}
public void OnPropertyChanged(string name)
{
if (PropertyChanged == null)
return;
var eventArgs = new PropertyChangedEventArgs(name);
PropertyChanged(this, eventArgs);
}
}
plus I have an enum OverviewType with None,Project,Person values.
The property changed event fired properly, but the user control is not refreshed. Could anyone enlight me, where is the flaw in my solution?
And the other question I have, how can I communicate from the usercontrols to the mainwindow viewmodel? Forex: the datagrid should be changed according to its filter.
Any help would be greatly appreciated. Cheers!
There are different problems here.
As Clemens said, you must fire your event after the value is updated. But it's not the main issue here.
Second problem: you are affecting your new usercontrol to the private member, so you're totally bypassing your property.
Replace
_userControl = new ProjectFilter();
by
this.UserControl = new ProjectFilter();
Third problem, which is not directly related to your question but actually is your biggest problem: you have an architecture design issue. You're exposing in your viewmodel a UserControl, which is an anti-pattern. Your viewmodel must not know anything about the view, so it must NOT have any reference to the controls inside the view. Instead of the binding you wrote, you could fire an event from the viewmodel and add an event handler in your view so it's your view that updates the usercontrol.
Try to fire the PropertyChanged after changing a property's backing field:
public UserControl UserControl
{
get { return _userControl; }
set
{
if (_userControl != value)
{
_userControl = value; // first
OnPropertyChanged("UserControl"); // second
}
}
}
Similar for OverviewType.