I'm new to WP8 and follow many tutorials. For parts of the menu I use a viewModel with NotifyPropertyChanged. When I get my list of news articles it creates a viewModel and displays it in a longListSelector.
But also I want to make 1 HubTile with the image and some preview-text of the first article. Is there a nice way to send some event to the .xaml.cs? Or do I have to make another viewModel for this one HubTile and make a binding?
Ony try was to make such a variable:
private bool _isDataLoaded = false;
public bool IsDataLoaded
{
get
{
return _isDataLoaded;
}
set
{
if (value != _isDataLoaded)
{
_isDataLoaded = value;
NotifyPropertyChanged("IsDataLoaded");
}
}
}
The same thing is used with "IsLoading"-variable to create a loading-indicator in the systemTray:
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
System.Diagnostics.Debug.WriteLine("MainPage_Loaded-Funktion");
Binding binding = new Binding("IsLoading") { Source = DataContext };
BindingOperations.SetBinding(
prog, ProgressIndicator.IsVisibleProperty, binding);
binding = new Binding("IsLoading") { Source = DataContext };
BindingOperations.SetBinding(
prog, ProgressIndicator.IsIndeterminateProperty, binding);
prog.Text = "Lade aktuelle Inhalte...";
}
Can I use this to call a function, when my variable is set and I get a notification?
The solution that helped me out was this:
<toolkit:HubTile Message="{Binding OnlineNews[0].TeaserText}"/>
Didn't know that you can access the viewModel like that. Thanks to Toni Petrina!
Related
I have simple ICommand-Bindings working, however I have Buttons inside an ItemsControl and wanted to get the sender information, like with the normal Routed-Events (object sender, e RoutedEventArgs) and this seems not to be possible with the normal ICommands, right?
I am a little bit lost here.
I currently use the Prism 6 DelegateCommand-Class to get things working. It looks like this:
private ICommand _selectCommand;
public ICommand SelectCommand
{
get
{
return _selectCommand ?? (_selectCommand = new DelegateCommand<object>(SelectImage));
}
}
private void SelectImage(object image)
{
var img = (BitmapImage)image;
var index = Scans.IndexOf(img);
this.CurrentIndex = index + 1;
ImageToDisplay = img;
}
How I can I get the RoutedCommand to work?
A view model is not supposed to be accessing or even know about any view element.
You should bind a target property of the control in the view to a source property of the view model that you can simply set in your SelectImage method when your command gets executed.
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.
I have a ObservableCollection<List<MessageView>> (MessageView is a custom class) I instantiate it that way
public ObservableCollection<List<MessageView>> _messagesView;
public ObservableCollection<List<MessageView>> messagesView {
get {
if (_messagesView == null) {
_messagesView = new ObservableCollection<List<MessageView>>();
}
return _messagesView;
}
set {
if (_messagesView != value) {
_messagesView = value;
PropertyChanged(this, new PropertyChangedEventArgs(nameof(messagesView)));
}
}
}
This property is set on a Singleton
I want to bind one of the item collection to a datagrid it would look that way in xaml:
<xmlns:module="clr-namespace:Myproject.MyNameSpace;assembly=Myproject">
<DataGrid
Name="DataGrid_messages"
...
ItemsSource="{Binding messagesView[2], Source={x:Static module:Singleton.Instance}}"
>
This is working well that way but this is not what I want to do. I want to have the control of my index. So I have to do the binding in c# with my controller but I never found an example to bind with a special index.
Binding myBinding = new Binding("messagesView");
myBinding.Source = Singleton.Instance;
myBinding.Path = ??
DataGrid_messages.SetBinding(DataGrid.ItemsSourceProperty, myBinding);
Share your thought about this, is it possible? Or a better way to do it?
UPDATE
Additional change to do with Clemens Answer:
The binding is set with the internal list so it's it which should be ObservableCollection type:
public List<ObservableCollection<MessageView>> messagesView;
Provided that the index is fixed, creating the binding path in code behind could look like this:
myBinding.Path = new PropertyPath(string.Format("messagesView[{0}]", index));
This is my first time posting a question. I'm looking into this issue for about a whole day but cannot see why this binding doesn't work.
I want a Label to display the name of a object "hotspot" which is a Property of class instance named Plan. There are multiple plans and each plan contains multiple hotspots. When I click on a hotspot the property Plan.SelectedHotSpot sets this clicked hotspot as value. If there is no HotSpot selected it turns to null.
XAML:
<Label Name="lblHotSpotName" />
MainWindow code behind when Plan is selected from ListBox:
private void lstPlans_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
canvas.Plan = PlanBLL.GetPlanByID(plans[lstPlans.SelectedIndex].ID);
lblHotSpotName.DataContext = canvas.Plan;
lblHotSpotName.SetBinding(Label.ContentProperty, "SelectedHotSpot.Name");
}
Plan class:
public class Plan : INotifyPropertyChanged
{
private HotSpot selectedHotSpot;
public HotSpot SelectedHotSpot
{
get { return selectedHotSpot; }
set
{
selectedHotSpot = value;
OnPropertyChanged("SelectedHotSpot");
OnPropertyChanged("SelectedHotSpot.Name");
}
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
This code doesn't seem to work when I click on a hotspot lblHotSpotName stays empty.
It seems to me that when a plan is loaded SelectedHotSpot is null and so it doesn't bind to that hotspot object which is selected after the plan has been loaded.
Is my insinuation right? That this binding needs to have an existing object which is not null. And when the object changes that we need to define the binding from label to Plan.SelectedHotSpot again.
Thanks for your help.
I can't be sure that I have understood your problem exactly right because your question is somewhat unclear, but can you not just data bind to the Label.Content property in XAML? If you want to data bind the SelectedHotSpot.Name property of the Plan item that is currently selected in the ListBox, then you should be able to do something like this:
<Label Name="lblHotSpotName"
Content="{Binding SelectedItem.SelectedHotSpot.Name, ElementName=lstPlans}" />
UPDATE >>>
You're still better off using XAML for your Binding. Add a string property to bind to and then update that in your lstPlans_SelectionChanged handler instead:
<Label Name="lblHotSpotName" Content="{Binding SelectedItemHotSpotName}" />
...
private void lstPlans_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
canvas.Plan = PlanBLL.GetPlanByID(plans[lstPlans.SelectedIndex].ID);
SelectedItemHotSpotName = canvas.Plan.SelectedHotSpot.Name;
}
I'm not sure it will help or not, but in lstPlans_SelectionChanged try this Binding:
var myBinding = new Binding();
myBinding.Path = new PropertyPath("SelectedHotSpot.Name");
myBinding.Source = canvas.Plan;
lblHotSpotName.SetBinding(Label.ContentProperty, myBinding);
If SelectedHotSpot.Name doesn't change, when this line is not needed:
OnPropertyChanged("SelectedHotSpot.Name");
in SelectedHotSpot property declaration.
Don't see any issue in the given code (though, Raise property changed for .Name is not required).
I would suggest to confirm that selectedHotSpot always has some instance and is not null.
Try modifying your plan class and set:
selectedHotSpot = new HotSpot(Name="Default")
and you should see "Default" in your label.
I have the same problem like this. But I´m using a DataGrid instead of a ListBox and it does not seem to work like this (it might also be because i never used visual basic and didnt translate the code correcly into c#).
I basicly want two DataGrids on the same data with different filters.
ICollectionView view_dataLinesUnfiltered;
ICollectionView view_dataLinesFiltered;
public MainWindow()
{
...
//view_dataLines = CollectionViewSource.GetDefaultView(dataLines); // <- Filter works on both
view_dataLinesUnfiltered = new CollectionView(dataLines); // <- Filter doesn´t work at all
view_dataLinesFiltered = new CollectionView(dataLines);
....
// Control Events
this.ShowAA.RaiseEvent(new RoutedEventArgs(System.Windows.Controls.Primitives.ToggleButton.UncheckedEvent));
}
private void ShowAA_Checked(object sender, RoutedEventArgs e)
{
view_dataLinesUnfiltered.Filter = null;
}
private void ShowAA_UnChecked(object sender, RoutedEventArgs e)
{
view_dataLinesUnfiltered.Filter = delegate(object o) { return FilterContent(o as ErrorDetection.stDataLine, "AA", ""); };
}
bool FilterContent(ErrorDetection.stDataLine line, string sFilterAA, string sFilter)
{
shortArrayToHexStringConverter converter = new shortArrayToHexStringConverter();
string comBuffer = converter.Convert(line.ComBufferP as object,typeof(string),0,System.Globalization.CultureInfo.CurrentCulture) as string;
return false;// !comBuffer.Contains("AA");
}
The FilterContent method is being called without problems, but the DataGrid shows the lines anyway. If I use GetDefaultView the Filter works on both Datagrids. Do I have to use some other view instead of CollectionView (ListCollectionView does also not work)?
i have made a small sample project to show the problem sample. It only consists of an constructor and an observable collection.
I got it to work somehow. I used CollectionViewSources now instead of ICollectionView.
<Window.Resources>
<CollectionViewSource x:Key="viewSource_dataLinesUnfiltered"/>
<CollectionViewSource x:Key="viewSource_dataLinesFiltered"/>
</Window.Resources>
...
<DataGrid Name="Filtered_Datagrid" ItemsSource="{Binding Source={StaticResource viewSource_dataLinesFiltered}}" >
...
</DataGrid>
...
<DataGrid Name="Unfiltered_Datagrid" ItemsSource="{Binding Source={StaticResource viewSource_dataLinesUnfiltered}}">
...
</DataGrid>
and the c Code:
CollectionViewSource viewSource_dataLinesUnfiltered;
CollectionViewSource viewSource_dataLinesFiltered;
...
public MainWindow()
{
InitializeComponent();
this.DataContext = dataLines;
viewSource_dataLinesUnfiltered = (CollectionViewSource)this.Resources["viewSource_dataLinesUnfiltered"];
viewSource_dataLinesUnfiltered.Source = dataLines;
viewSource_dataLinesFiltered = (CollectionViewSource)this.Resources["viewSource_dataLinesFiltered"];
viewSource_dataLinesFiltered.Source = dataLines;
// Control Events
this.ShowAA.RaiseEvent(new RoutedEventArgs(System.Windows.Controls.Primitives.ToggleButton.UncheckedEvent));
}
private void ShowAA_Checked(object sender, RoutedEventArgs e)
{
viewSource_dataLinesUnfiltered.View.Filter = null;
}
private void ShowAA_UnChecked(object sender, RoutedEventArgs e)
{
viewSource_dataLinesUnfiltered.View.Filter = delegate(object o) { return FilterContent(o as ErrorDetection.stDataLine, "AA", ""); };
}
bool FilterContent(ErrorDetection.stDataLine line, string sFilterAA, string sFilter)
{
shortArrayToHexStringConverter converter = new shortArrayToHexStringConverter();
string comBuffer = converter.Convert(line.ComBufferP as object,typeof(string),0,System.Globalization.CultureInfo.CurrentCulture) as string;
return !comBuffer.Contains("AA");
}
But I´m not sure why it works this way and the filter is not applied on window repaints when ICollectionView is used.
You need to specify which ICollectionVIew is used on which DataGrid.
If you just bind to the collection (dataLines in this case) WPF will use the 'default view' (or create one if necessary), this is why the first commented out line works for filtering.
There are a few ways you could specify which view is used for which datagrid, depending on what patterns, etc. you are using
1) Like the linked question, you could set the ItemsSource for each DataGrid in the window's code behind, after initializing the views, e.g.:
filteredDataGrid.ItemsSource = view_dataLinesFiltered;
unfilteredDataGrid.ItemsSource = view_dataLinesUnfiltered;
2) You could set the DataContext of the window to itself, or make a view model for the screen that contains the view, and make the views public properties and then bind to the intended view for each grid, e.g.
<DataGrid ItemsSource="{Binding View_dataLinesFiltered}"> ....
Edit:
Now I'm not at work and can get to dropbox and play with your example it seems like the cause of the weird behaviour is the use of CollectionView directly. On the msdn page for CollectionView it says
You should not create objects of this class in your code. To create a
collection view for a collection that only implements IEnumerable,
create a CollectionViewSource object, add your collection to the
Source property, and get the collection view from the View property.
However, if you don't want to set up the views in XAML, you could also change your CollectionViews to ListCollectionViews and it should work as expected (this is likely the view type that CollectionViewSource is making for you behind the scenes anyway).