Die ListeUnderlyingListwird in der View tatsächlich nicht verwendet (diese benötige ich im ViewModel).
Underlying1 und Underlying2 haben in der View beide eine Bindung (im geposteten Code aber nur Underlying1 ersichtlich). Das diese beiden nicht aktualisiert werden ist genau mein Problem, dass ich nicht zu lösen weiss. Für Terminerinnerungen habe ich in einem anderen Teil des Programms nahezu denselben (nachfolgenden) Code, er funktioniert und die View aktualisiert sich nach dem Aufruf der "Load"-Methode. Der einzige Unterschied ist, dass die dort verwendeten Controls nicht in einem UserControl eingebettet sind.
ObservableCollection<Reminder> loadedReminderList = PersistenceInstance.Load();
if (loadedReminderList != null)
{
ReminderList.Clear();
foreach (Reminder reminder in loadedReminderList)
{
ReminderList.Add(reminder);
}
}
Deklaration der UnderlyingList
UnderlyingList = new ObservableCollection<Underlying>()
{
Underlying1, Underlying2
};
Deklaration der 2 Underlying-Objekte
Underlying1 = new Underlying()
{
DivDate = DivDate,
DivAmount = DivAmount,
VrfList = { new DateTime(2023, 01, 20), new DateTime(2025, 11, 28) },
RowNumber = 1,
RowIsVisible = true,
BS = 'K',
Hdt = new DateTime(2022, 05, 21),
Prjd = new DateTime(2022, 05, 21),
Vrf = new DateTime(2025, 11, 28),
KUl = 68.63f,
Ka = 1,
BzvhO = 100.00f,
Vortrag = 0f,
Colorgroup = IAsset.Group.B
};
Underlying2 = new Underlying()
{
DivDate = DivDate,
DivAmount = DivAmount,
VrfList = { new DateTime(2023, 01, 20), new DateTime(2025, 11, 28) },
RowNumber = 2,
RowIsVisible = true,
BS = 'K',
Hdt = new DateTime(2022, 05, 21),
Prjd = new DateTime(2022, 05, 21),
Vrf = new DateTime(2025, 11, 28),
KUl = 68.63f,
Ka = 1,
BzvhO = 100.00f,
Vortrag = 0f,
Colorgroup = IAsset.Group.C
};
Hoffe das hilft für das Verständnis vom Gesamten weiter.
Besten Dank auch für deine Randbemerkung, die ich gerne noch studieren werden.
Hallo zusammen
Ich habe das Problem, dass ich bei der Deserialisierung mit Json sowohl die Bindings als auch die Commands verliere (vorher funktionieren diese einwandfrei). Das Objekt bekommt die richtigen Daten zwar wieder, diese werden in den einzelnen Controls die sich in einem UserControl befinden aber nicht mehr aktualisiert (Bild im Anhang: Mit "1" und "2" können die UserControls in grün und gelb ein-/ausgeschaltet werden. Die Daten in den Controls kommen von den "Underlying"-Objekten.).
MainWindow.xaml
<uc:UnderlyingsRowHidden Grid.Row="0" Visibility="{Binding Underlying1.RowIsVisible, Converter={StaticResource invertedBooleanToVisibilityConverter}}" />
<uc:UnderlyingsRow Grid.Row="0"
Visibility="{Binding Underlying1.RowIsVisible, Converter={StaticResource booleanToVisibilityConverter}}"
Underlying="{Binding Underlying1}"
ButtonBuySellUnderlyingCommand="{Binding ButtonBuySellUnderlyingCommand}"
CalculateUnderlyingCommand="{Binding CalculateUnderlyingCommand}"
ComboBoxColorgroupUnderlyingCommand="{Binding ComboBoxColorgroupUnderlyingCommand}"
CalculateCommandParameter="{Binding Underlying1.RowNumber}"/>
MainViewModel.cs
public MainViewModel()
{
ShowUnderlyingsRowCommand = new RelayCommand(ShowUnderlyingsRowExecute, ShowUnderlyingsRowCanExecute);
...
}
private void ButtonLoadExecute(object obj)
{
ObservableCollection<Underlying> loadedUnderlyingList = PersistenceInstance.Load();
if (loadedUnderlyingList != null)
{
UnderlyingList.Clear();
foreach (Underlying underlying in loadedUnderlyingList)
{
UnderlyingList.Add(underlying);
}
}
}
Persistence.cs
public ObservableCollection<Underlying>? Load()
{
FileStream fs;
StreamReader sr;
OpenFileDialog ofd = new OpenFileDialog()
{
InitialDirectory = @"C:\Temp",
Filter = "Json (*.json)|*.json",
Title = "Datei zum Öffnen auswählen : "
};
if (ofd.ShowDialog() == true)
{
if (!File.Exists(ofd.FileName))
{
MessageBox.Show("Datei " + ofd.FileName + " existiert nicht");
return null;
}
fs = new FileStream(ofd.FileName, FileMode.Open);
sr = new StreamReader(fs);
jsonUnderlyingList = sr.ReadLine();
sr.Close();
}
//Liste muss noch auf null geprüft werden, sonst Absturz, wenn zwar geöffnet aber nichts ausgewählt wird.
ObservableCollection<Underlying>? newUnderlyingList = JsonConvert.DeserializeObject<ObservableCollection<Underlying>>(jsonUnderlyingList);
return newUnderlyingList;
}
RelayCommand.cs
public class RelayCommand : ICommand
{
//Private Fields
private readonly Action<object> _executedHandler;
private readonly Predicate<object> _canExecuteHandler;
private Action onButtonPressed;
//Constructors
public RelayCommand(Action<object> execute) : this(execute, null)
{
}
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("Execute kann nicht null sein");
_executedHandler = execute;
_canExecuteHandler = canExecute;
}
public RelayCommand(Action onButtonPressed)
{
this.onButtonPressed = onButtonPressed;
}
//Methods
public void Execute(object parameter)
{
_executedHandler(parameter);
}
public bool CanExecute(object parameter)
{
if (_canExecuteHandler == null)
return true;
return _canExecuteHandler(parameter);
}
//Events
public event EventHandler? CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}
Vielen Dank für eure Hilfe!
Hallo zusammen
Aus meinem MainViewModel rufe ich die folgende Methode auf:
private void TextBoxSymbolExecute(object obj)
{
Option option1 = Option1;
TWSInterface.DataForOptionsRow(Symbol, ref option1);
MessageBox.Show("");
option1.Stk = option1.StkList[0];
}
Nun ist es so, dass option1.Stk nur einen Wert erhält, wenn die darüberliegende Zeile "Messagebox.Show(""); besteht. Ansonsten erhalte ich eine SystemOutOfRange-Exception, weil option1.Stk null ist, was es nicht sein soll.
Kann mir jemand helfen wie ich das (Thread)-Problem umgehen/lösen kann?
Nachfolgend noch den Code der TWSInterface-Klasse:
public class TWSInterface : EWrapperImpl
{
//Eigenschaften
int contractUnderlyingID;
ObservableCollection<float> stkList;
//Konstruktoren
public TWSInterface()
{
stkList = new ObservableCollection<float>();
}
//Methoden
//Call to TWS-API
public void Connect()
{
ClientSocket.eConnect("", 7496, 0);
var reader = new EReader(ClientSocket, Signal);
reader.Start();
new Thread(() =>
{
while (ClientSocket.IsConnected())
{
Signal.waitForSignal();
reader.processMsgs();
}
})
{ IsBackground = true }.Start();
while (NextOrderId <= 0) { }
}
public void DataForOptionsRow(string symbol, ref Option zo)
{
ClientSocket.reqContractDetails(100, contractUnderlying);
zo.StkList = stkList;
}
//Recall from TWS-API
public override void contractDetails(int reqId, ContractDetails contractDetails)
{
printContractMsg(contractDetails.Contract);
}
public override void printContractMsg(Contract contract)
{
contractUnderlyingID = contract.ConId;
ClientSocket.reqSecDefOptParams(0, contract.Symbol, "", "STK", contractUnderlyingID);
}
public override void securityDefinitionOptionParameter(int reqId, string exchange, int underlyingConId, string tradingClass, string multiplier, HashSet<string> expirations, HashSet<double> strikes)
{
//Sicherstellung, dass der Zugriff auf die ObservableCollection auf dem UI-Thread erfolgt
Application.Current.Dispatcher.BeginInvoke(() =>
{
stkList.Clear();
foreach (double strike in strikes)
{
float strikeFloat = (float)strike;
stkList.Add(strikeFloat);
}
});
}
}
Nun habe ich den DecimalUpDown genommen und einfach die SpinnerButtons entfernt. Das Resulat: Es funktioniert und sieht wie eine normale TextBox aus. Fantastisch.
Ja das ist auch wieder wahr. Daher habe ich ein passendes Control von Extended.WPF.Toolkit implementiert, wobei ich nun aber wieder beim alten Problem angelangt bin, dass "LostFocus" zwar funktioniert aber das Drücken der "Return-Taste" nicht.
<xctk:AutoSelectTextBox Style="{StaticResource styleRowTextBox}" Grid.Column="3" Margin="2 4" Foreground="Black" Text="{Binding ElementName=userControlOptionsRow, Path=Option.KUl, Mode=TwoWay}" AutoSelectBehavior="OnFocus">
<i:Interaction.Triggers>
<i:EventTrigger EventName="LostFocus">
<i:InvokeCommandAction Command="{Binding ElementName=userControlOptionsRow, Path=CalculateCommand}"
CommandParameter="{Binding ElementName=userControlOptionsRow, Path=CalculateCommandParameter}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</xctk:AutoSelectTextBox>
Ist denn das normal, dass alles so zusammengebastelt werden muss? Ich möchte ja nur eine einfach TextBox in einem UserControl, dessen dahinterliegende Eigenschaft angepasst wird, wenn ich die Return-Taste drücke. Habe ich (noch) komplett etwas nicht verstanden oder ist das wirklich so mühsam? Bzw. woher weiss ich für was ich was einsetzen muss und wo die Grenzen von den einzelnen Elementen/Controls liegen usw....
Das ganze kann noch viel einfacher gelöst werden und funktioniert (StringFormat und UpdateSourceTrigger):
<TextBox Style="{StaticResource styleRowTextBox}" Grid.Column="3" Foreground="Black" Text="{Binding ElementName=userControlOptionsRow, Path=Option.KUl, Mode=TwoWay,StringFormat={}{##.##},UpdateSourceTrigger=PropertyChanged}">
<TextBox.InputBindings>
<KeyBinding Key="Return"
Command="{Binding ElementName=userControlOptionsRow, Path=CalculateCommand}"
CommandParameter="{Binding ElementName=userControlOptionsRow, Path=CalculateCommandParameter}"/>
</TextBox.InputBindings>
</TextBox>
Freude herrscht!
Das funktioniert mit deiner Variante bestens. Anscheinend hat sich dein weiterer Beitrag mit meinem überschnitten, sonst wäre es schon vorher klar gewesen. Vielen Dank für deine geduldige und sehr wertvolle Hilfe.
Wenn du mir noch eine letzte Frage erlaubst: Die TextBox aktualisiert den Wert erst (wie standardmässig vorgesehen) bei LostFocus. Also wenn ich den Focus wechsle, zur TextBox zurückkehre und dann "Return" drücke. Mit "PropertyChanged" geht das ohne den Fokuswechsel. Dann kann ich in die TextBox aber lediglich noch ganze Zahlen eingeben wie z.B. 45 und nicht etwa 45.15.
Da ich Anfänger bin, ist leider noch gar nichts klar 😉
Was du meinst habe ich verstanden, bei der Umsetzung happert es aber leider schon den ganzen Nachmittag. Liegt es an der Bindung im MainWindow.xaml an CalculateOptionCommand?
MainWindow.xaml:
<uc:OptionsRow Grid.Row="0" Visibility="{Binding Option1.OptionsRowIsVisible, Converter={StaticResource booleanToVisibilityConverter}}" DataContext="{Binding Option1}"
CalculateOptionCommand="{Binding RelativeSource={RelativeSource AncestorType=vm:MainViewModel}, Path=CalculateOptionCommand}"/>
OptionsRow.xaml (TextBox im UserControl):
<TextBox Style="{StaticResource styleRowTextBox}" Grid.Column="3" Foreground="Black" Text="{Binding KUl, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<TextBox.InputBindings>
<KeyBinding Key="Return" Command="{Binding CalculateOptionCommand}" CommandParameter="{Binding RowNumber, Mode=OneWay}"/>
</TextBox.InputBindings>
</TextBox>
OptionsRow.xaml.cs (Codebehind vom UserControl):
private static readonly DependencyProperty RowNumberProperty = DependencyProperty.Register("RowNumber", typeof(int), typeof(OptionsRow), new PropertyMetadata(0));
private static readonly DependencyProperty CmdProperty = DependencyProperty.Register("CalculateOptionCommand", typeof(RelayCommand), typeof(OptionsRow));
public int RowNumber
{
get { return (int)GetValue(RowNumberProperty); }
set { SetValue(RowNumberProperty, value); }
}
public RelayCommand CalculateOptionCommand
{
get { return (RelayCommand)GetValue(CmdProperty); }
set { SetValue(CmdProperty, value); }
}
Vielen Dank für deine Hilfestellung!
Ursprünglich habe ich das mit dem DataContext ebenfalls so gelöst wie von dir vorgeschlagen. Da der Zugriff auf CalculateOptionCommand dann aber nicht funktionierte, habe ich das Ganze auf die von mir beschriebene Variante gelöst. Hast du mir einen Vorschlag wie ich das Problem lösen kann, damit der Zurgriff auf CalculateOptionCommand auch mit geändertem DataContext wieder funktioniert?