binding slider value in MVVM - wpf

I have a problem in slider value data binding in MVVM. When value gets changed my expected value isn’t achieved. How can I solve my problem?
I have a listbox, a slider and a textblock. listbox is bound to ListImage, slider value and textblock text is bound to CurrentImage. One button with command navigate the lisbox item. CurrentImage is a property in the viewmodel. When I change slider’s setter, new value of setter put to current value of slider’s setter and the arrangement of listbox gets corrupted. For example when value of my slider’s setter set to 50 and I change value of slider to 10 again. My slider value navigate from 10 to 50 and not more. It must be navigate whole of the listbox but it can’t. There is my code:
XAML:
<TextBlock Text="{Binding CurrentImage.Index}"/>
<Slider Height="23" HorizontalAlignment="Left" Margin="12,305,0,0" Name="slider1" VerticalAlignment="Top" Width="479" Maximum="{Binding ListImage.Count, Mode=OneTime}"
Value="{Binding CurrentImage.Index, Mode=TwoWay}"
SmallChange="1" />
<Button Content="{Binding DisplayPlay}" Command="{Binding PlayCommand}" Height="23" HorizontalAlignment="Left" Margin="507,305,0,0" Name="button1" VerticalAlignment="Top" Width="75" />
<ListBox Height="129" HorizontalAlignment="Left" Margin="12,334,0,0" ItemsSource="{Binding ListImage}" SelectedItem="{Binding CurrentImage,Mode=TwoWay}"
VerticalAlignment="Top" Width="472">
viewmodel:
public class MainViewModel : ViewModelBase
{
public ICommand PlayCommand { get; set; }
private DispatcherTimer _Timer;
public ImageDTO Image { get; set;}
DataAccess AC = new DataAccess();
public byte[] Bytes { get; set; }
public MainViewModel()
{
ListImage = AC.OpenImages();
CurrentImage = ListImage[0];
Bytes = CurrentImage.Bytes;
this.PlayCommand = new DelegateCommand(Play, CanPlay);
DisplayPlay = "Play";
_Timer = new DispatcherTimer();
_Timer.Interval = new TimeSpan(0, 0, 0, 0, 2000 / 30);
_Timer.Tick += new EventHandler(timer_Tick);
}
private string _DisplayPlay;
public string DisplayPlay
{
get { return _DisplayPlay; }
set
{
if (_DisplayPlay != value)
{
_DisplayPlay = value;
OnPropertyChanged("DisplayPlay");
}
}
}
private List<ImageDTO> _ListImage;
public List<ImageDTO> ListImage
{
get { return _ListImage; }
set
{
if (_ListImage != value)
_ListImage = value;
OnPropertyChanged("ListImage");
}
}
private ImageDTO _CurrentImage;
public ImageDTO CurrentImage
{
get { return _CurrentImage; }
set
{
if (_CurrentImage != value)
{
_CurrentImage = value;
OnPropertyChanged("CurrentImage");
}
}
}
public bool CanPlay(object parameter)
{
return true;
}
public void Play(object parameter)
{
if (DisplayPlay == "Play")
{
DisplayPlay = "Pause";
_Timer.Start();
}
else
{
_Timer.Stop();
DisplayPlay = "Play";
}
}
private void timer_Tick(object sender, EventArgs e)
{
int position = ListImage.FindIndex(x => x.Index == CurrentImage.Index);
position++;
if (position == ListImage.Count)
{
position = 0;
}
else
{
CurrentImage = ListImage[position];
}
}

Maybe this is what you want:
<StackPanel>
<ListBox x:Name="ImageListBox" IsSynchronizedWithCurrentItem="True">
<ListBoxItem>Image1</ListBoxItem>
<ListBoxItem>Image2</ListBoxItem>
<ListBoxItem>Image3</ListBoxItem>
<ListBoxItem>Image4</ListBoxItem>
</ListBox>
<Slider Value="{Binding ElementName=ImageListBox, Path=SelectedIndex}"
Maximum="{Binding ElementName=ImageListBox, Path=Items.Count}"/>
</StackPanel>
You probably want to handle the max value nicer than in this sample

In your code the value of slider is bound to CurrentImage index, so when you change the value of slider then the index of the current image will be changed. Assumed that the current image index is 5 then your slider’s value will be 5 then if you move the slider pointer to a position like 10 then the Current image index will be set to 10 it means that your current image will be modified, so it means that it would not navigate to the 10th element of list, but it modifies the index of the image (with index of 5) and set its index to 10, in other words, you would not have a image with index of 5 anymore and 2 of your images are going to have the same index (10).
As a solution, you can add another property like “Index” into your viewmodel, and bind the slider value to that one, so at get method return the index of the selected item in list, and at the set method change the selected item in the list and call OnPropertyChanged and pass “Index” as the parameter.

Related

How to set specific item in a ListView using MVVM?

I have a ListView of Button elements like this:
<ListView ItemsSource="{Binding DummyModelList}" SelectedItem="{Binding SelectedItem}">
<ListViewItem >
<Button Name="test" Grid.Row="0" Grid.Column="10" Grid.ColumnSpan="4" Grid.RowSpan="4" VerticalAlignment="Center" Background="Transparent" Command="{Binding DataContext.TestCommand, RelativeSource={RelativeSource FindAncestor, AncestorType=ListView}}">
<Button.Template>
<ControlTemplate>
<Grid RenderTransformOrigin="0.5,0.5" x:Name="bg">
<Image Source="{Binding DataContext.SlidOnOffImg , RelativeSource={RelativeSource FindAncestor, AncestorType=ListView}}"/>
</Grid>
</ControlTemplate>
</Button.Template>
</Button>
</ListViewItem >
In my ViewModel:
public class MyViewModel:BindableBase
{
private ObservableCollection<Slide> dummyModelList;
public ObservableCollection<Slide> DummyModelList
{
get { return dummyModelList; }
set
{
dummyModelList = value;
OnPropertyChanged(() => DummyModelList);
}
}
public string SlidOnOffImg { get; set; }
private int selectedItem;
public int SelectedItem //this property stores currently selected item from thumbnails list
{
get { return selectedItem; }
set
{
selectedItem = value;
OnPropertyChanged(() => SelectedItem);
}
public CompositeCommand TestCommand { get; set; }
public MyViewModel(IRegionManager regionManager, IEventAggregator eventAggregator) //constructor
{
dummyModelList = new ObservableCollection<ISlide>() { new Slide(), new Slide(), new Slide()};
TestCommand = new CompositeCommand();
TestCommand.RegisterCommand(new DelegateCommand(ChangeImage));
}
private void ChangeImage()
{
if (dummyModelList.ElementAt(selectedItem).SlideExcluded)
dummyModelList.ElementAt(selectedItem).SlideExcluded = false;
else
dummyModelList.ElementAt(selectedItem).SlideExcluded = true;
SlidOnOffImg = dummyModelList.ElementAt(selectedItem).SlideONOFFImgPath;
OnPropertyChanged(() => SlidOnOffImg);
}
}
In my model:
public class Slide : ISlide
{
public bool SlideExcluded { get; set; }
public string SlideONOFFImgPath
{
get
{
if(SlideExcluded)
return "/Assets/Visible_OFF.png";
else
return "/Assets/Visible_ON.png";
}
}
}
My goal is to have button's image to toggle between two image paths on button click. It works, but the problem is that all the buttons in the list change the image path on some button click. I want only the one that is clicked to change the image path.
How to solve this?
You need to be binding to SelectedIndex instead of SelectedItem in your XAML. SelectedIndex holds a reference to the index for the item in the list, SelectedItem holds a reference to the object in the list. Either that, or change your view model to work with the actual object rather than the index.

How do I prevent keyboard navigation in an AutoCompleteBox from firing the SelectionChanged event?

I'm trying to build a search field using the AutoCompleteBox from the WPF Toolkit. The AutoCompleteBox's Text property is bound to a property in a ViewModel that implements INotifyPropertyChanged. When the property is changed, it fetches new suggestions to show to the user.
This gets mucked up if the user uses arrow keys to scan through the list of autocomplete suggestions before choosing one - the moment the cursor move into the popup, SelectionChanged is fired, the text field gets a new value, and the autocomplete suggestions are re-collected. This also interferes with my desire to use the SelectionChanged event to kick off a search.
Is there any way to prevent the SelectionChanged event from firing on keyboard navigation?
Here's how I have things set up. Note sc:SearchField is a subclass of AutoCompleteBox that only provides a way to access the TextBox property on the AutoCompleteBox so I can call functions like SelectAll()
XAML:
<sc:SearchField x:Name="SearchField" DataContext="{Binding SearchBoxVm}" Text="{Binding Query, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" ItemsSource="{Binding QuerySuggestions, UpdateSourceTrigger=PropertyChanged}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}" IsTextCompletionEnabled="False" Margin="54,10,117,67" Grid.RowSpan="2" BorderThickness="0" FontSize="14" PreviewKeyUp="searchField_OnKeyup" Foreground="{Binding Foreground, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" FontStyle="{Binding QueryFont, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" >
</sc:SearchField>
ViewModel:
void GetQuerySuggestions()
{
if (!string.IsNullOrEmpty(Query) && !Query.Equals(DEFAULT_TEXT))
{
QueryFont = FontStyles.Normal;
Foreground = Brushes.Black;
QuerySuggestions = SearchAssistant.GetQueryRecommendations(_query);
}
}
public string _query = DEFAULT_TEXT;
public string Query
{
get
{
return _query;
}
set
{
_query = value;
GetQuerySuggestions();
NotifyPropertyChanged("Query");
}
}
List<string> querySuggestions = new List<string>();
public List<string> QuerySuggestions
{
get { return querySuggestions; }
set
{
querySuggestions = value;
NotifyPropertyChanged("QuerySuggestions");
}
}
SearchField subclass:
public class SearchField : AutoCompleteBox
{
public TextBox TextBox
{
get
{
return (this.GetTemplateChild("Text") as TextBox);
}
}
}
Not sure if this is what you are wanting to do but I have the following code which only changes the selection when the 'Enter' key is pressed or the mouse is used to select an item from the list (left mouse button clicked). I can arrow up and down the list without issue and only fire the selection changed event when the user presses enter or clicks on the desired entry.
Note that I am using the AutoCompleteBox and not the SearchField as you are using.
In XAML:
<toolkit:AutoCompleteBox Name="OmniSearchTextBox"
ItemsSource="{Binding CompanyList}"
SelectedItem="{Binding SelectedObject, Mode=TwoWay}"
IsTextCompletionEnabled="False"
FilterMode="Contains"
KeyUp="OmniSearch_KeyUp"
MouseLeftButtonUp="OmniSearch_MouseLeftButtonUp"
Margin="10,94,10,0"
RenderTransformOrigin="0.518,1.92" Height="35"
VerticalAlignment="Top" />
In code behind:
private void OmniSearch_KeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
BindingExpression exp = this.OmniSearchTextBox.GetBindingExpression(AutoCompleteBox.SelectedItemProperty);
exp.UpdateSource();
}
}
private void OmniSearch_MouseLeftButtonUp(object sender, MouseEventArgs e)
{
BindingExpression exp = this.OmniSearchTextBox.GetBindingExpression(AutoCompleteBox.SelectedItemProperty);
exp.UpdateSource();
}
In ViewModel:
private const string CompanyListPropertyName = "CompanyList";
private ObservableCollection<Company> _companyList;
public ObservableCollection<Company> CompanyList
{
get
{
return _companyList;
}
set
{
if (_companyList == value)
{
return;
}
_companyList = value;
RaisePropertyChanged(CompanyListPropertyName);
}
}
private Company _selectedObject;
public Company SelectedObject
{
get
{
return _selectedObject;
}
set
{
if (_selectedObject != value)
{
_selectedObject = value;
}
}
}

Silverlight ComboBox force reselect SelectedItem

I've got a list of items bound to a ComboBox. When a user selects an item, I'd like to cancel the selection and select a different item instead. This must happen from within the setter of the property that the SelectedItem is bound to. I'm using Silverlight 3.
My data model for each item in the ComboBox:
public class DataItem
{
public int Id { get; set; }
public string Name { get; set; }
}
Object that is set to the DataContext:
public class DataContainer : INotifyPropertyChanged
{
public DataContainer()
{
itemList = new List<DataItem>();
itemList.Add(new DataItem() { Id = 1, Name = "First" });
itemList.Add(new DataItem() { Id = 2, Name = "Second" });
itemList.Add(new DataItem() { Id = 3, Name = "Third" });
}
public event PropertyChangedEventHandler PropertyChanged;
private DataItem selectedItem;
public DataItem SelectedItem
{
get { return selectedItem; }
set
{
if (value != null && value.Id == 2)
value = itemList[0];
selectedItem = value;
NotifyPropertyChanged("SelectedItem");
}
}
private List<DataItem> itemList;
public List<DataItem> ItemList
{
get { return itemList; }
set { itemList = value; NotifyPropertyChanged("DataList"); }
}
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Relevant bits of xaml:
<StackPanel>
<StackPanel Orientation="Horizontal">
<ComboBox x:Name="comboBox" DisplayMemberPath="Name" Width="100" ItemsSource="{Binding ItemList}" SelectedItem="{Binding Path=SelectedItem, Mode=TwoWay}"/>
<Button Content="Set to First" Width="100" Click="Button_Click"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Selected item: "/>
<TextBlock Text="{Binding SelectedItem.Id}"/>
<TextBlock Text=" - "/>
<TextBlock Text="{Binding SelectedItem.Name}"/>
</StackPanel>
</StackPanel>
It looks like my code to select the first item when the user selects the second item is working. The selected item is in fact set to "First" while the ComboBox is still displaying "Second" as if it was selected.
Is there any way to force the ComboBox to redraw or to reconsider what it should visually mark as selected?
I do this from the above mentioned Button_Click method and it works:
private void Button_Click(object sender, RoutedEventArgs e)
{
var c = DataContext as DataContainer;
if (c != null)
{
c.SelectedItem = null;
c.SelectedItem = c.ItemList[0];
}
}
But setting to null and then the value I want doesn't work if I do it from within the setter like I need to.
I may have found a solution for you. I was able to get what I think you're asking for working by doing the following:
public DataItem SelectedItem
{
get { return _selectedItem; }
set
{
if (value != null && value.Id == 2)
{
value = itemList[0];
UpdateUI(); // Call this to force the UI to update.
}
_selectedItem = value;
NotifyPropertyChanged("SelectedItem");
}
}
private void UpdateUI()
{
ThreadPool.QueueUserWorkItem(
o =>
{
Thread.Sleep(1);
Deployment.Current.Dispatcher.BeginInvoke(()=>
{
_selectedItem = null;
NotifyPropertyChanged("SelectedItem");
_selectedItem = itemList[0];
NotifyPropertyChanged("SelectedItem");
});
});
}
I wish I could explain to you why this is working, but I can only guess. Basically, its exiting the UI thread, and then re-entering a moment later via the Dispatcher.BeginInvoke() call. This appears to give the ComboBox control time to update itself from the user interaction, and then respond to the Dispatcher execution.
One problem I've found is that Silverlight seems to go a little wonky after multiple executions of the threading code. Increasing the Thread.Sleep time seems to help. I think this solution will work for the majority of situations and won't be an issue.
You don't have to queue a Thread with 1 second waiting as grimus suggested. this should also work for you:
public DataItem SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
NotifyPropertyChanged("SelectedItem");
if (value != null && value.Id == 2)
{
Diployment.Current.Dispatcher.BeginInvoke(() => {
_selectedItem = itemList[0];
NotifyPropertyChanged("SelectedItem"); });
}
}
}

WPF MVVM Combox not retaining old value on Validation fail

i have a combox control which is bound to a property using MVVM. There is validation done in the set method on value change.. The problem is the value getting changed to new value even if the validation fails and not retaining the old value..
Below is the XAML:
<ComboBox Grid.Column="1" Grid.Row="1" Width="200" ItemsSource="{Binding Path=Applications, Mode=OneTime}" SelectedItem="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Path=DataContext.Application, Mode=TwoWay}" Margin="3"></ComboBox>
Below is the View Model Code:
private string[] types = new string[] { "A", "B" };
private string application;
public ObservableCollection<string> Applications { get; private set; }
public Const() {
this.Applications = new ObservableCollection<string>(this.types.ToList());
}
public string Application {
get {
this.application = this.applicationSpecificRequirements.ContainsKey(Resources.ApplicationKey) ? this.applicationSpecificRequirements[Resources.ApplicationKey] : this.Applications[0];
return this.application;
}
set {
if (this.exchangeViewModel.CheckIfApplicationNameExistsOrIsEmptyAndAssign(this.InstanceName, value)) {
System.Windows.Application.Current.Dispatcher.BeginInvoke(
new Action(() => {
this.applicationSpecificRequirements[Resources.ApplicationKey] = this.application;
((IHaveOnPropertyChangedMethod) this).OnPropertyChanged("Application");
}), DispatcherPriority.ContextIdle, null);
return;
}
this.applicationSpecificRequirements[Resources.ApplicationKey] = value;
}
}
looks like you're missing OnPropertyChanged(...) at last line in property setter.

WPF: ComboBox SelectedIndex = -1?

I am using a MVVM Wizard with several pages. When I set a value in the combobox and go to the next page and switch back I want to reset the value I set before.
But all whats happening is that the combobox is empty at top and the index is -1 ?
What do I wrong?
<ComboBox ItemsSource="{Binding Path=LessonNumbers}" SelectedIndex="{Binding SelectedLessonNumber}" />
private ReadOnlyCollection<int> _lessonNumbers;
public ReadOnlyCollection<int> LessonNumbers
{
get
{
if (_lessonNumbers == null)
this.CreateLessonNumbers();
return _lessonNumbers;
}
}
private void CreateLessonNumbers()
{
var list = new List<int>();
for (int i = 1; i < 24; i++)
{
list.Add(i);
}
_lessonNumbers = new ReadOnlyCollection<int>(list);
}
private int _selectedLessonNumber;
public int SelectedLessonNumber
{
get { return _selectedLessonNumber; }
set
{
if (_selectedLessonNumber == value)
return;
_selectedLessonNumber = value;
this.OnPropertyChanged("SelectedLessonNumber");
}
}
UPDATE:
<ComboBox
SelectedIndex="0"
SelectedItem="{Binding SelectedWeeklyRotationNumber}"
ItemsSource="{Binding Path=WeeklyRotationNumbers}"
Height="23"
HorizontalAlignment="Left"
Margin="336,212,0,0"
VerticalAlignment="Top"
Width="121"
MaxDropDownHeight="100"
IsReadOnly="True"
IsTextSearchEnabled="False"
/>
private ReadOnlyCollection _weeklyRotationNumbers;
public ReadOnlyCollection WeeklyRotationNumbers
{
get
{
if (_weeklyRotationNumbers == null)
this.CreateWeeklyRotationNumbers();
return _weeklyRotationNumbers;
}
}
private void CreateWeeklyRotationNumbers()
{
var list = new List<string>();
list.Add("No rotation");
for (int i = 1; i < 16; i++)
list.Add(i.ToString());
_weeklyRotationNumbers = new ReadOnlyCollection<string>(list);
}
private string _selectedWeeklyRotationNumber;
public string SelectedWeeklyRotationNumber
{
get { return _selectedWeeklyRotationNumber; }
set
{
if (_selectedWeeklyRotationNumber == value)
return;
_selectedWeeklyRotationNumber = value;
this.RaisePropertyChanged("SelectedWeeklyRotationNumber");
Messenger.Default.Send<string>(value);
}
}
Again, what do I wrong or what is wrong with the string property?
Change XAML SelectedIndex to SelectedItem:
<ComboBox ItemsSource="{Binding Path=LessonNumbers}"
SelectedItem="{Binding SelectedLessonNumber}" />
UPDATE:
Somewhere you must set the DataContext of your Window to reference the collection from your XAML.
In my case I typically do that in the constructor of my view.
// this my class containing WeeklyRotationNumbers
private MainViewModel _mvm;
public MainView()
{
InitializeComponent();
_mvm = new MainViewModel();
DataContext = _mvm;
}
I added string to the read only collections:
private ReadOnlyCollection<string> _weeklyRotationNumbers;
public ReadOnlyCollection<string> WeeklyRotationNumbers
I also implemented the interface INotifyPropertyChanged which I think you did, but you are likely using
a different base class to handle the PropertyChanged event.
Everthing else I cut and paste from your code.

Resources