I use AutoCompleteBox in MVVM and i want to execute something only if the user click on the Item or if the user press Enter.
But now when I use the down\Up Key on the keyboard the selectedItem property changes...
My controls :
<Controls:AutoCompleteBox ItemsSource="{Binding IndicationDtos, Mode=TwoWay}"
Width="100" SelectedItem="{Binding IndicationSelected, Mode=TwoWay}"
ValueMemberPath="Diagnosis" Text="{Binding Criteria, Mode=TwoWay}" MinimumPopulateDelay="250"/>
What can I do to make the property "SelectedItem" is assigned only on Enter or click?
If you have any question...
thanks a lot
In your SelectedItem binding, you can use:
SelectedItem="{Binding IndicationSelected, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"
That way selected item only changes when you focus on something else
I found solution i created new class.
Like this :
public class AutoCompleteBoxEx : AutoCompleteBox
{
public static readonly DependencyProperty SelectionBoxItemProperty =
DependencyProperty.Register(
"SelectionBoxItem",
typeof(object),
typeof(AutoCompleteBox),
new PropertyMetadata(OnSelectionBoxItemPropertyChanged));
public object SelectionBoxItem
{
get
{
return GetValue(SelectionBoxItemProperty);
}
set
{
SetValue(SelectionBoxItemProperty, value);
}
}
protected override void OnDropDownClosing(RoutedPropertyChangingEventArgs<bool> e)
{
base.OnDropDownClosing(e);
SelectionBoxItem = SelectionAdapter.SelectedItem;
}
private static void OnSelectionBoxItemPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
}
Related
I want to be able to add an event from my custom control to the outside world
as I change a value in my custom control, I use TwoWay, but I also want an event to be generated in my main app
I just cannot find anything relevant to this
the main app xaml contains this line :
<custom:MyComp x:Name="myComp" ValueChanged="SomeFunction" Value="{ Binding Path=someIntVariable, Mode=TwoWay}"></custom:MyComp>
how do I implement a ValueChanged event in the custom control ?
my control xaml:
<UserControl ...>
<StackPanel Orientation="Horizontal" >
...
<TextBox x:Name="MyCompTextBox" Width="50" Height="20" TextChanged="MyCompTextBox_TextChanged"/>
...
</StackPanel>
</UserControl>
the code
public partial class MyComp: UserControl
{
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(int), typeof(MyComp), new FrameworkPropertyMetadata(0, OnValuePropertyChangedCallback));
public int Value
{
get { return (int)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
private static void OnValuePropertyChangedCallback(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
MyComp mMyComp= source as MyComp;
int newValue=(int)e.NewValue;
myComp.MyCompTextBox.Text = newValue.ToString();
}
public MyComp()
{
InitializeComponent();
}
private void MyCompTextBox_TextChanged(object sender, EventArgs e)
{
int _bpm = int.ParseMyCompTextBox.Text);
Value = _bpm;
MyCompTextBox.Text = Value.ToString();
// here I want to trigger an even to the main app !!!
}
}
thanks for your help
I am trying to learn MVVVM, by doing a weather application, using a Udemy course as a reference, the Bing Maps Api, and the OpenWeather API.
I am trying to bind the city with the text of the AutoSuggestBox of my xaml
I have done the WeatherVM and bind it to the view, as a page resource
I also did a quick Method that gets me the City and the Country code, where I am (Using the Bing Maps api)
I Called the method from the MainPage.cs, only to see if it works, and it work fine I get the City and Country Code as expected
WeatherVM
public OpenWeather OpenWeather { get; set; }
private Task<string> _city;
public Task<string> city {
get { return _city; }
set {
_city = value;
GetLocationData();
}
}
public WeatherVM() {
OpenWeather = new OpenWeather();
}
private async void GetLocationData() {
var cityData = await MapLocator.GetCityData();
}
}
}
MainPage.xaml
x:Class="MVVM_Example.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MVVM_Example"
xmlns:vm="using:MVVM_Example.ViewModel"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Page.Resources>
<vm:WeatherVM x:Key="vm" />
</Page.Resources>
<Grid DataContext="{StaticResource vm}">
<AutoSuggestBox Margin="40" QueryIcon="Find"
PlaceholderText="Search"
Text="{Binding Source={StaticResource vm}, Path=city, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</Page>
I expect "Orlando" to appear in my AutoSuggestBox
When you use Binding, you need to set the DataContext of the page to be an instance of your binding source class instead of set the page.Resource. For more details, you can refer to this document.
<Page.DataContext>
<local:WeatherVM x:Name="viewModelInDataContext"/>
</Page.DataContext>
<Grid>
<AutoSuggestBox Margin="40" QueryIcon="Find"
PlaceholderText="Search"
Text="{Binding Path=city, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
In code-behind, suppose you need to assign a value to the city.
this.viewModelInDataContext.city = "Orlando";
Update:
The ViewModel you showed has some issues. First, you need to assign a value to the cityData property, if not, the text of AutoSuggestBox which bound with the cityData will always display empty. Second, you need to implement INotifyChanged in WeatherVM. When your cityData changes, the text of the AutoSuggestBox will change. Third, do not set the property type to asynchronous like Task, this will affect the display, it's better to set string cityData.
public class WeatherVM : INotifyPropertyChanged
{
public WeatherVM(){
GetData();
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private string _cityData;
public string cityData
{
get
{
return _cityData;
}
set
{
_cityData = value;
OnPropertyChanged();
}
}
private async void GetData()
{
cityData = await MapLocator.GetCityData();
}
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
I have some problem with correct binding my folder with images to master details and other operation with them.
So, I have model of folder and image
public class AppFolder
{
private long id;
private List<AppImage> appImages;
public AppFolder() { }
public List<AppImage> AppImages { get => appImages; set => appImages = value; }
public long Id { get => id; set => id = value; }
}
public class AppImage
{
private int id;
private string title;
private ImageSource appImageURL;
public AppImage() { }
public AppImage(string title, ImageSource imageSource)
{
Title = title;
AppImageURL = imageSource;
}
public int Id { get => id; set => id = value; }
public string Title { get => title; set => title = value; }
public ImageSource AppImageURL { get => appImageURL; set => appImageURL = value; }
}
And I bind List to Master/Details.
public class UserPhotosViewModel : ViewModelBase
{
private readonly IDataService dataService;
private readonly INavigationService navigationService;
public UserPhotosViewModel(IDataService dataService, INavigationService navigationService)
{
this.dataService = dataService;
this.navigationService = navigationService;
Initialize();
}
private async Task Initialize()
{
var item = new List<AppFolder>();
try
{
item = await dataService.GetDataList();
FolderList = item;
}
catch (Exception ex)
{
}
}
private List<AppFolder> folderList;
public List<AppFolder> FolderList
{
get { return folderList; }
set
{
folderList = value;
RaisePropertyChanged(nameof(FolderList));
}
}
}
Example xaml file
<controls:MasterDetailsView ItemsSource="{Binding FolderList}">
<controls:MasterDetailsView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Id}"></TextBlock>
</DataTemplate>
</controls:MasterDetailsView.ItemTemplate>
<controls:MasterDetailsView.DetailsTemplate>
<DataTemplate>
<controls:AdaptiveGridView ItemsSource="{Binding AppImages}"
OneRowModeEnabled="False"
ItemHeight="205"
DesiredWidth="205"
SelectionMode="Multiple"
Margin="0 6 0 0"
>
<controls:AdaptiveGridView.ItemTemplate>
<DataTemplate >
<Grid Background="White" Margin="10">
<Image
Source="{Binding AppImageURL}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Stretch="Uniform"
/>
<TextBlock Text="{Binding TextTitle}"></TextBlock>
</Grid>
</DataTemplate>
</controls:AdaptiveGridView.ItemTemplate>
</controls:AdaptiveGridView>
</DataTemplate>
</controls:MasterDetailsView.DetailsTemplate>
</controls:MasterDetailsView>
So, it's work correct and I saw my folders with images on page
enter image description here
Look nice and I think it all.
But when I want to add event and SelectedItem to AdaptiveGridView from MVVM model, I saw that it doesn't see them. Visual Studio show me that I could to write them in Model "AppFolder" but it's nonsens....
So, my question: How I can add event (binding command/method) to adaptive grid from UserPhotosViewModel?
Thank you for your time.
UPDATE
enter image description here
2) User double click on image and program send folder with this image and other to page and binding them to FlipView (imitation full screen viewer)
I try to add, but that way also offers to write event and property in model. I use this way for settings in navigation panel (parent control for master/details)
You just need to add command property in your AppFolder class like the following:
public class AppFolder:ViewModelBase
{
private long id;
private List<AppImage> appImages;
public AppFolder() { }
public List<AppImage> AppImages { get => appImages; set => appImages = value; }
public long Id { get => id; set => id = value; }
public RelayCommand<object> relayCommand { get; set; }
}
Then, in your UserPhotosViewModel, you could declare a method for initializing this command.
Since I do not know what the dataService.GetDataList() is. I just change this place in your code and make a simple code sample for you.
private void Initialize()
{
var item = new List<AppFolder>();
try
{
List<AppImage> ls = new List<AppImage>();
ls.Add(new AppImage() { Id = 1, Title = "aaa", AppImageURL = new BitmapImage(new Uri("ms-appx:///Assets/1.jpg")) });
ls.Add(new AppImage() { Id = 2, Title = "bbb", AppImageURL = new BitmapImage(new Uri("ms-appx:///Assets/2.jpg")) });
item.Add(new AppFolder() { Id = 1, AppImages = ls,relayCommand=new RelayCommand<object>(DoubleTapCommand) });
FolderList = item;
}
catch (Exception ex)
{
}
}
private void DoubleTapCommand(object obj)
{
//the obj will be an AppFolder object
}
<controls:MasterDetailsView.DetailsTemplate>
<DataTemplate>
<controls:AdaptiveGridView x:Name="grid" ItemsSource="{Binding AppImages}"
OneRowModeEnabled="False"
ItemHeight="205"
DesiredWidth="205"
SelectionMode="Multiple"
Margin="0 6 0 0">
<Interactivity:Interaction.Behaviors>
<Interactions:EventTriggerBehavior EventName="DoubleTapped">
<Interactions:InvokeCommandAction Command="{Binding relayCommand}" CommandParameter="{Binding}"></Interactions:InvokeCommandAction>
</Interactions:EventTriggerBehavior>
</Interactivity:Interaction.Behaviors>
<controls:AdaptiveGridView.ItemTemplate>
<DataTemplate >
<Grid Background="White" Margin="10">
<Image
Source="{Binding AppImageURL}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Stretch="Uniform"
/>
<TextBlock Text="{Binding TextTitle}"></TextBlock>
</Grid>
</DataTemplate>
</controls:AdaptiveGridView.ItemTemplate>
</controls:AdaptiveGridView>
</DataTemplate>
</controls:MasterDetailsView.DetailsTemplate>
1) User select N images and delete them (example)
About this requirement, I suggested that you'd better add a button to bind command for deleting selected items.
I have selected text from a ListBox that I want to insert into a RichTextBox at the caret position. I can get the selected text to be inserted at the end of the text string.
I am not sure how to pass the RichTextBox caret position to my view model.
Here is part of my code for the project.
<Button x:Name="AddItemBtn" Content="Add Item" HorizontalAlignment="Left" Margin="417,10,0,0" VerticalAlignment="Top" Width="100" Command="{Binding AddItemBtn}" CommandParameter="{Binding ElementName=AddItemList,Path=SelectedItem}"/>
<wpftoolkit:RichTextBox Grid.Column="0" Text="{Binding TestText, UpdateSourceTrigger=PropertyChanged}" x:Name="MyEditor" ScrollViewer.VerticalScrollBarVisibility="Auto" Margin="0" Height="Auto" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Width="Auto" IsDocumentEnabled="True" AcceptsTab="True" AcceptsReturn="True" >
<wpftoolkit:RichTextBox.Resources>
<Style TargetType="{x:Type Paragraph}">
<Setter Property="Margin" Value="0" ></Setter>
<Setter Property="FontSize" Value="15"></Setter>
</Style>
</wpftoolkit:RichTextBox.Resources>
<wpftoolkit:RichTextBox.TextFormatter>
<wpftoolkit:PlainTextFormatter/>
</wpftoolkit:RichTextBox.TextFormatter>
</wpftoolkit:RichTextBox>
Here is the view model part.
private string _testText;
public string TestText
{
get
{
return _testText;
}
set
{
//_testText = _testText + value;
SetProperty(ref _testText, value);
}
}
public ICommand AddItemBtn
{
get;
set;
}
public void addItem(Tabbed selectedItem)
{
if (selectedItem != null)
{
MessageBox.Show(selectedItem.Command);
if (TestText != null)
{
TestText = TestText.ToString() + selectedItem.Command;
}
else
{
TestText = selectedItem.Command;
}
}
}
I tried a flowdocument but still could not get the parameters to pass correctly.
I like to put a function on the view model that is setup in the view code behind.
public class MainViewModel : ViewModelBase
{
public Func<int> GetCarrotPosition { get; set; }
//...
Looks like you can get the number of characters into the text string by getting the offset from the document start start to the current position
public MainWindow()
{
// InitializeComponent stuff..
var castedContext = (MainViewModel)DataContext;
castedContext.GetCarrotPosition = () =>
{
// Placing the cursor at the start of the text returns a value of 2, so I subtract 2 to get the current cursor location
return MyRichTextBox.CaretPosition.DocumentStart.GetOffsetToPosition(MyRichTextBox.CaretPosition) - 2;
};
//...
Finally, call the GetCarrotPosition() function in your command
var carrotPosition = GetCarrotPosition();
TestText.Insert(carrotPosition, selectedItem.Command);
Creating delegates on the view model that get wired up in the view code behind is the sexiest MVVM way of working with UI elements I know of.
This code shows that the Delegate Command from the Visual Studio MVVM template works differently when used with MenuItem and Button:
with Button, the command method has access to changed OnPropertyChanged values on the ViewModel
when using MenuItem, however, the command method does not have access to changed OnPropertyChanged values
Does anyone know why this is the case?
MainView.xaml:
<Window x:Class="TestCommand82828.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:TestCommand82828.Commands"
Title="Main Window" Height="400" Width="800">
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="_File">
<MenuItem Command="{Binding DoSomethingCommand}" Header="Do Something" />
</MenuItem>
</Menu>
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top">
<Button Command="{Binding DoSomethingCommand}" Content="test"/>
<TextBlock Text="{Binding Output}"/>
<TextBox Text="{Binding TheInput}"/>
</StackPanel>
</DockPanel>
</Window>
MainViewModel.cs:
using System;
using System.Windows;
using System.Windows.Input;
using TestCommand82828.Commands;
namespace TestCommand82828.ViewModels
{
public class MainViewModel : ViewModelBase
{
#region ViewModelProperty: TheInput
private string _theInput;
public string TheInput
{
get
{
return _theInput;
}
set
{
_theInput = value;
OnPropertyChanged("TheInput");
}
}
#endregion
#region DelegateCommand: DoSomething
private DelegateCommand doSomethingCommand;
public ICommand DoSomethingCommand
{
get
{
if (doSomethingCommand == null)
{
doSomethingCommand = new DelegateCommand(DoSomething, CanDoSomething);
}
return doSomethingCommand;
}
}
private void DoSomething()
{
Output = "did something, the input was: " + _theInput;
}
private bool CanDoSomething()
{
return true;
}
#endregion
#region ViewModelProperty: Output
private string _output;
public string Output
{
get
{
return _output;
}
set
{
_output = value;
OnPropertyChanged("Output");
}
}
#endregion
}
}
I think what you're seeing is because MenuItems don't take focus away from a TextBox, and by default, a TextBox only pushes changes back to its bound source when focus shifts away from it. So when you click the button, the focus shifts to the button, writing the TextBox's value back to _theInput. When you click the MenuItem, however, focus remains in the TextBox so the value is not written.
Try changing your TextBox declaration to:
<TextBox Text="{Binding TheInput,UpdateSourceTrigger=PropertyChanged}"/>
Or alternatively, try switching to DelegateCommand<t>, which can receive a parameter, and pass the TextBox's text to it:
<MenuItem Command="{Binding DoSomethingCommand}"
CommandParameter="{Binding Text,ElementName=inputTextBox}" />
...
<TextBox x:Name="inputTextBox" Text="{Binding TheInput}" />