I´ve a question regarding INotifyTaskCompletion (https://msdn.microsoft.com/en-us/magazine/dn605875.aspx).
When i use this patter in an UI, created in XAML everything works fine.
But when i want to use it in a UI generated by Code (C#) it doesen´t work.
Viewmodel is the same.
I have have a Test project in this Link.
https://github.com/marcuskammerlander/NotifyTaskCompletion
You can change the UI in App.xaml.cs
public Testpage()
{
//Work
this.BindingContext = new testpageViewModel();
TestCount.SetBinding(Label.TextProperty, new Binding("UrlByteCount.Result", BindingMode.OneWay));
//Doesent Work
//testpageViewModel viewModel = new testpageViewModel();
//this.BindingContext = viewModel;
//TestCount.SetBinding(Label.TextProperty, new Binding(nameof(viewModel.UrlByteCount.Result), BindingMode.OneWay));
Content = new StackLayout
{
Children = {
TestLabel, TestCount
}
};
}
Regards,
Marcus
Related
I am working on a new UWP application that interacts with some hardware via Bluetooth. Using the windows-universal-samples repo as a guide I was able to sucessfully get what I wanted working.
Now I am trying to refactor the code I wrote in a click event handler into a view model class using Prism. However I don't know how to approach this. In other scenarios where I need to pass data between a View and ViewModel I would create a property on the ViewModel and bind it to the control in the view's XAML.
The problem is that Windows.Devices.Enumaration.DevicePicker is used in a way that doesn't seem compatible with the MVVM pattern. In the click handler, the data and control are merged together and I don't see how I can make some kind of list property on the view model and then bind it to the view. Here is the simplest example of the code I am working with:
async void DiscoverButton_OnClick(object sender, RoutedEventArgs e)
{
var devicePicker = new DevicePicker();
devicePicker.Filter.SupportedDeviceSelectors.Add(BluetoothLEDevice.GetDeviceSelectorFromPairingState(true));
// Calculate the position to show the picker (right below the buttons)
var ge = DiscoverButton.TransformToVisual(null);
var point = ge.TransformPoint(new Point());
var rect = new Rect(point, new Point(100, 100));
var device = await devicePicker.PickSingleDeviceAsync(rect);
var bluetoothLEDevice = await BluetoothLEDevice.FromIdAsync(device.Id);
}
See PickSingleDeviceAsync() creates a control directly.
Now I am trying to refactor the code I wrote in a click event handler into a view model class using Prism. However I don't know how to approach this.
You could bind command for your button and use CommandParameter to pass parameter to the command.
Please refer to the following code sample for details:
<Button x:Name="btn" Content="device" Command="{Binding ClickCommand}" CommandParameter="{Binding ElementName=btn}"></Button>
public class MianViewModel : BindableBase
{
public ICommand ClickCommand
{
get;
private set;
}
public MianViewModel()
{
ClickCommand = new DelegateCommand<object>(ClickedMethod);
}
private async void ClickedMethod(object obj)
{
var devicePicker = new DevicePicker();
devicePicker.Filter.SupportedDeviceSelectors.Add(BluetoothLEDevice.GetDeviceSelectorFromPairingState(true));
// Calculate the position to show the picker (right below the buttons)
Button DiscoverButton = obj as Button;
if (DiscoverButton != null)
{
var ge = DiscoverButton.TransformToVisual(null);
var point = ge.TransformPoint(new Point());
var rect = new Rect(point, new Point(100, 100));
var device = await devicePicker.PickSingleDeviceAsync(rect);
if (device != null)
{
var bluetoothLEDevice = await BluetoothLEDevice.FromIdAsync(device.Id);
}
}
}
}
The solution I came up with was to abandon the built in UI provided by DevicePicker and instead create my own UI to use with DeviceWatcher. For example:
void StartWatcher()
{
ResultCollection.Clear();
string selector = BluetoothLEDevice.GetDeviceSelector();
DeviceWatcher = DeviceInformation.CreateWatcher(selector);
DeviceWatcher.Added += async (deviceWatcher, deviceInformation) =>
{
await OnUiThread(() =>
{
ResultCollection.Add(deviceInformation);
});
};
DeviceWatcher.Start();
}
Where ResultCollection would be bound from the view model to the view.
The third or so page in my app contains a ListView, but the list for some reason doesn't display until I either toggle the view (which switches the views ItemSource to another list) or rotate the screen.
If I do the toggle twice (so back to the original starting state) the listview is there still. It seems like a bug but I haven't been able to find anything on it.
public partial class ReviewRequestsPage : ContentPage
{
private readonly List<RequestCell> closedRequestCells = new List<RequestCell>();
private readonly List<RequestCell> openRequestCells = new List<RequestCell>();
public ReviewRequestsPage()
{
InitializeComponent();
NavigationPage.SetHasNavigationBar(this, false);
BindingContext = new SvgImagesViewModels();
new Footer().SetGestureRecognizers(null, Notifications, Help, Home, this);
LoadRequestLists();
ToggleSwitch.PropertyChanged += (o, ea) => { HandleToggle(((Switch) o).IsToggled); };
}
....
private void LoadRequestLists()
{
UserDialogs.Instance.ShowLoading("Loading Requests...", MaskType.Black);
var client = new RestService().Client;
var request =
new RequestService().GetAllRequests();
client.ExecuteAsync(request, response =>
{
var myList = JsonConvert.DeserializeObject<List<Request>>(response.Content, new DateTimeConverter());
myList.ForEach(r =>
{
if (r.status.type == StatusType.CLOSED) closedRequestCells.Add(new RequestCell(r));
else if (r.status.type != StatusType.DELETED) openRequestCells.Add(new RequestCell(r));
});
UserDialogs.Instance.HideLoading();
RequestsList.ItemSource = openRequestCells;
});
}
private void HandleToggle(bool isToggled)
{
Switchlabel.Text = isToggled ? Constants.Closed : Constants.Open;
RequestsList.ItemsSource = isToggled ? closedRequestCells : openRequestCells;
}
Is there something else I should be calling or doing so that the listview appears once I set the ItemSource? It doesn't make sense why it wouldn't be already though. Also nothing is failing and everything is working as expected, other than that
The constructor does not set ItemsSource, at least not immediately. It calls LoadRequestLists that starts an async Task which will eventually set ItemsSource, so at some point in the future, ItemsSource will be set (whenever the Rest response is received and the UI thread happens to run).
Since constructors cannot await an async Task, you will need to refactor your code so that the Rest client runs (and finishes) before the constructor, and so the ReviewRequestsPage will take in the List as a parameter. Then the constructor can build the openRequestCells and closedRequestCells and assign to ItemsSource.
In a dummy WinForms app, I'm able to create a ListBox at design time, create a background thread at runtime, and then add controls to the ListBox from the background thread. But if I did the same in WPF, I get an error.
Why am I am able to do this in WinForms, but not WPF? Is my WinForm example not the same as the WPF one? Or is there indeed a reason why it works just fine in WinForms and not WPF?
WinForms:
private List<Label> _labels;
public Form1()
{
InitializeComponent();
Thread test = new Thread(DoStuff);
test.SetApartmentState(ApartmentState.STA);
test.Start();
}
private void DoStuff()
{
_labels = new List<Label>();
_labels.Add(new Label() { Text = "Label1" });
_labels.Add(new Label() { Text = "Label2" });
_labels.Add(new Label() { Text = "Label3" });
if (listBox1.InvokeRequired)
{
listBox1.Invoke((MethodInvoker)delegate { listBox1.DataSource = _labels; });
}
else
{
listBox1.DataSource = _labels;
}
}
WPF:
public partial class MainWindow : Window
{
private ObservableCollection<Label> _labels;
public MainWindow()
{
InitializeComponent();
Thread test = new Thread(DoStuff);
test.SetApartmentState(ApartmentState.STA);
test.Start();
}
private void DoStuff()
{
_labels = new ObservableCollection<Label>();
_labels.Add(new Label() { Content = "Label1" });
_labels.Add(new Label() { Content = "Label2" });
_labels.Add(new Label() { Content = "Label3" });
this.Dispatcher.BeginInvoke((Action)(() =>{ icMain.ItemsSource = _labels; }));
}
}
This is the error I receive. Pretty standard and expected:
WinForms isn't as strict about checking for cross-threading issues. This is probably because WinForms doesn't actually have controls such as labels. Rather, they are just wrappers around the real controls that are implemented at the OS level.
Since this is an implementation detail, there is no guarantee that your WinForms code will continue to work in the future. (That said, it isn't under active development so it will probably continue working.)
I suppose that adding childs, or ever modifying properties of controls is bad practice at WinForms too. WPF just has more advanced ways to control access to UI from another threads, and warns you in early stage.
Related question.
I have a created a window ( WPF and MVVM ) - say PrintWidow ( so I have PrintWindow.xaml , PrintWindow.xaml.cs , PrintWindowViewModel.cs- viewmodel)
Now I am going to use(call) this PrintWindow obj from some other class on button click or on some command trigger , I want to set Document Source for this PrintWindow(following MVVM).
How would I do this ? I created a PrintDocument object in PrintWindow.xaml.cs and tried to bind it as follows : (obviously just a blank try - as I cannot do this declaration in XAML)
private PrintDocument printDocuementView;
public PrintDocument PrintDocuement
{
get { return printDocuementView; }
set { printDocuementView = value; }
}
//constructor
public PrintWindow()
{
InitializeComponent();
this.DataContext = new PrintViewModel();
Binding b = new Binding();
b.Source = printDocuementView;
b.Path = new PropertyPath("PrintDocumentCommand"); // "PrintDocumentCommand" is defined in View Model class and is responsible to set the `PrintDocument` object there.
}
This code (obviously) doesn't work. How should I go about it.
Summary : I want to open PrintWindow from another window and eventually set some Property of PrintWindow from code behind of the 'other widow' object.The query is - where should this property go? View ? ViewModel? ?? puzzzeled
I have googled for answers - but couldn't relate any to my problem.
I am a Freshman for WPF and a Rookie for MVVM.
Since your PrintDocumentCommand is in your PrintViewModel but you're setting the Source of this Binding to an instance of the PrintDocument-Class, it can't be found, because the Binding is looking for the PrintDocumentCommand in PrintDocument-Class.
If you want to open the PrintWindow from another Window, place the PrintDocument-Property and the PrintDocumentCommand in the ViewModel of the other Window. Now your function that is executed through the PrintDocumentCommand could look like:
private void Print()
{
PrintWindow pw = new PrintWindow(PrintDocument);
pw.ShowDialog();
}
The constructor of your PrintView could be like:
public PrintWindow(PrintDocument pd)
{
InitializeComponents();
this.DataContext = new PrintViewModel(pd);
}
and now you can access the PrintDocument in your PrintViewModel.
I have a business application (created from template) and I can change language dynamically by making ResourceWrapper INotifyPropertyChanged and then adding in code:
private void Language_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
Thread.CurrentThread.CurrentCulture =
new CultureInfo(((ComboBoxItem)((ComboBox)sender).SelectedItem).Tag.ToString());
Thread.CurrentThread.CurrentUICulture =
new CultureInfo(((ComboBoxItem)((ComboBox)sender).SelectedItem).Tag.ToString());
((ResourceWrapper)App.Current.Resources["ResourceWrapper"]).ApplicationStrings =
new ApplicationStrings();
}
this works fine on resources referenced/binded in xaml files (i.e. MainPage frame), but it does not update references of anything I have declared in code i.e.
InfoLabel.Content = ApplicationStrings.SomeString
At the moment I'm not using ResourceWrapper. My question here is how can I change my code so it uses it and updates when ResourceWrapper change. I tried:
InfoLabel.Content = ((ResourceWrapper)App.Current.Resources["ResourceWrapper"])
.ApplicationStrings.SomeString
but it doesn't work.
Any ideas?
You would have to create a Binding in code. Something like this:
var b = new Binding("SomeString");
b.Source = ((ResourceWrapper)App.Current.Resources["ResourceWrapper"]).ApplicationStrings;
b.Mode = BindingMode.OneWay;
InfoLabel.SetBinding(ContentControl.ContentProperty, b);
Keep in mind that the class you bind to must implement INotifyPropertyChanged.
EDIT:
If you are worried about the amount of code, just create a helper method somewhere in your app:
public Binding GetResourceBinding(string key)
{
var b = new Binding(key);
b.Source = ((ResourceWrapper)App.Current.Resources["ResourceWrapper"]).ApplicationStrings;
b.Mode = BindingMode.OneWay;
return b;
}
And then use the helper method like this:
InfoLabel.SetBinding(ContentControl.ContentProperty, GetResourceBinding("SomeString"));